Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

SwiftUI view as a UITextView inputAccessoryView has incorrect frame

I am trying to add a SwiftUI view as an accessory view to a UITextView through a UIHostingController. Following is my UIInputView configuration

struct AccessoryView: View { /* SwiftUI View */ } 

class AccessoryInputView: UIInputView {

    private let controller: UIHostingController<AccessoryView>

    init(_ attachmentItem: Binding<AttachmentItemType>) {
        let parentView = AccessoryView(selectedAttachmentItem: attachmentItem)
        self.controller = UIHostingController<AccessoryView>(rootView: parentView)
        super.init(frame: CGRect(x: 0, y: 0, width: 0, height: 44), inputViewStyle: .default)

        self.controller.view.translatesAutoresizingMaskIntoConstraints = false
        self.controller.view.backgroundColor = UIColor.clear
        self.addSubview(self.controller.view)

        self.layer.borderWidth = 2
        self.layer.borderColor = UIColor.blue.cgColor
    }

    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    override func layoutSubviews() {
        super.layoutSubviews()
        NSLayoutConstraint.activate([
            self.widthAnchor.constraint(equalTo: self.controller.view.widthAnchor),
            self.heightAnchor.constraint(equalTo: self.controller.view.heightAnchor),
            self.centerXAnchor.constraint(equalTo: self.controller.view.centerXAnchor),
            self.centerYAnchor.constraint(equalTo: self.controller.view.centerYAnchor)
        ])
    }
}

I have drawn the border of the UIInputView in blue and the have set a border around the AccessoryView in red. As you can see in the screenshot the SwiftUI view is offset by couple points. UITextView has the borders in green.

Any reason why this happens?

ScreenShot

like image 634
Sudara Avatar asked Sep 12 '25 14:09

Sudara


2 Answers

I managed to fix this by overriding AccessoryInputView's safeAreaInsets like this:

struct AccessoryView: View { /* SwiftUI View */ } 

class AccessoryInputView: UIInputView {

    private let controller: UIHostingController<AccessoryView>

    init(_ attachmentItem: Binding<AttachmentItemType>) {
        let parentView = AccessoryView(selectedAttachmentItem: attachmentItem)
        self.controller = UIHostingController<AccessoryView>(rootView: parentView)
        super.init(frame: CGRect(x: 0, y: 0, width: 0, height: 44), inputViewStyle: .default)

        self.controller.view.translatesAutoresizingMaskIntoConstraints = false
        self.controller.view.backgroundColor = UIColor.clear
        self.addSubview(self.controller.view)

        self.layer.borderWidth = 2
        self.layer.borderColor = UIColor.blue.cgColor
    }

    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    // HERE
    override var safeAreaInsets: UIEdgeInsets {
        .zero
    }

    override func layoutSubviews() {
        super.layoutSubviews()
        NSLayoutConstraint.activate([
            self.widthAnchor.constraint(equalTo: self.controller.view.widthAnchor),
            self.heightAnchor.constraint(equalTo: self.controller.view.heightAnchor),
            self.centerXAnchor.constraint(equalTo: self.controller.view.centerXAnchor),
            self.centerYAnchor.constraint(equalTo: self.controller.view.centerYAnchor)
        ])
    }
}
like image 117
Wessley Avatar answered Sep 14 '25 03:09

Wessley


I took @wes's answer and used generics and a builder to make it more reusable:

In essence, I replaced the AccessoryView struct with an AccessoryContent generic constrained to be a SwiftUI View and then aded a builder method to the init method.

To use, all you need is:

uiView.inputAccessoryView = AccessoryInputView {
  Text("Hello!")
}

AccessoryInputView.swift:

class AccessoryInputView<AccessoryContent: View>: UIInputView {
    private let controller: UIHostingController<AccessoryContent>

    init(_ accessoryViewBuilder: () -> AccessoryContent ) {
        controller = UIHostingController<AccessoryContent>(rootView: accessoryViewBuilder())
        super.init(frame: CGRect(x: 0, y: 0, width: 0, height: 44), inputViewStyle: .default)

        controller.view.translatesAutoresizingMaskIntoConstraints = false
        controller.view.backgroundColor = UIColor.clear
        addSubview(controller.view)
    }

    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    override var safeAreaInsets: UIEdgeInsets {
        .zero
    }

    override func layoutSubviews() {
        super.layoutSubviews()
        NSLayoutConstraint.activate([
            widthAnchor.constraint(equalTo: controller.view.widthAnchor),
            heightAnchor.constraint(equalTo: controller.view.heightAnchor),
            centerXAnchor.constraint(equalTo: controller.view.centerXAnchor),
            centerYAnchor.constraint(equalTo: controller.view.centerYAnchor)
        ])
    }
}
like image 22
Jason Armstrong Avatar answered Sep 14 '25 04:09

Jason Armstrong