Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

SwiftUI = UIViewControllerRepresentable Update Text via @Binding

Tags:

swift

swiftui

From SwiftUI I am trying to use a button to implement a text change in UIViewControllerRepresentable but may be missing a key concept about binding.

In SwiftUI:

struct MainView: View {
 @State private var textHere = "Set up text"
  var body: some View {
   return VStack{
    DisplayView(text: $textHere)
    Button(action:{
     self.textHere = "Button update text" 
    }) {
     HStack {
      Text("Change the text by clicking")
      .background(Color.white)
     }
    }
    .background(Color.blue)
   }
  }
}

I then have a UIViewControllerRepresentable:

struct DisplayView: UIViewControllerRepresentable  {
 @Binding var text: String

  func makeUIViewController(context: UIViewControllerRepresentableContext< DisplayView >) -> UIViewController {
   let controller = DisplayViewController()
   controller.text = text
   return controller
  }

  func updateUIViewController(_ uiViewController: UIViewController, context: UIViewControllerRepresentableContext< DisplayView >) {
   let controller = DisplayViewController()
   controller.text = text
   controller.updateText()
  }
}

My ViewController:

class CameraViewController : UIViewController {
 var text = "Hello"
 var textViewTest = UILabel()

 func updateText(){
  print("update text " + text)
  self.textViewTest = UILabel(frame: CGRect(x: 40.0, y: 120.0, width: 250.0, height: 100.0))
  ...
  self.textViewTest.text = text
 }

 override func viewDidLoad() {
  super.viewDidLoad()
  self.textViewTest = UILabel(frame: CGRect(x: 40.0, y: 120.0, width: 250.0, height: 100.0))
  ...
  self.textViewTest.text = text
  view.addSubview(self.textViewTest)
 }
}

I have tried a few variations in the updateText() function.

The data IS being passed along from the button click:

  • "Button update text" is passed into the updateUIViewController method of UIViewControllerRepresentable
  • "Button update text" is passed into the updateText() function of the UIViewController
  • "Button update text" is passed into viewDidLoad of the UIViewController

But the value pass along does not get displayed as a change on the label.

It seems possible that the update is made within the ViewController but the SwiftUI view is not updated?

EDIT:

This article pointed me in the direction of 'wrapping' UIRepresentable. This is the new version:

    struct DisplayView: UIViewControllerRepresentable  {
     typealias UIViewControllerType = CameraViewController
     var text: String?

     func makeUIViewController(context: UIViewControllerRepresentableContext< DisplayView >) -> CameraViewController {
      print("view called")
      return CameraViewController()
     }

     func updateUIViewController(_ cameraViewController: CameraViewController, context: UIViewControllerRepresentableContext< DisplayView >) {
      print("View updated")
      cameraViewController.text = text
     }
    }

And the ViewController has been updated:

final class CameraViewController : UIViewController {
 var text: String? {
  didSet {
   updateController()
  }
 }

 private var ccmeraViewController: CameraViewController?

 func updateController(){
  let textViewTest = UILabel(frame: CGRect(x: 40.0, y: 120.0, width: 250.0, height: 100.0))
  guard let text = text else { return }

  textViewTest.center = self.view.center
  textViewTest.textAlignment = NSTextAlignment.justified
  textViewTest.textColor = UIColor.blue
  textViewTest.backgroundColor = UIColor.lightGray
  textViewTest.text = text
  view.addSubview(textViewTest)
 }

 override func viewDidLoad() {
  super.viewDidLoad()
  //updateController()
 }

All great! We can now properly pass a String value from SwiftUI to the ViewController and update the label.

HOWEVER, when you first open the page BOTH makeUIViewController and updateUIViewController get called, and so two instances of the label appear on top of each other.

So the final step is to figure out how to only display the UILabel once.

Update 3 and Partial Solution

A minimum repository with the solution can be found here.

The answer was correct, you need to add @Binding. However, what's interesting is that binding a String through to a ViewController which displays a label results in TWO instances of the label appearing in the view.

By binding a UILabel from SwiftUI through to the UIViewControllerRepresentable solves the issue, however the label changes position when the String value is updated.

It's not clear to me: a) why UIViewControllerRepresentable receives two requests to update the view or b) why the UILabel changes position.

As well, it doesn't make a lot of sense to bind a UILabel back from SwiftUI. This was meant to be a test of embedding ViewControllers and using them to display a label isn't what you'd actually want to DO and yet why these issues happens doesn't make a lot of sense.

like image 925
dot3 Avatar asked Oct 31 '25 04:10

dot3


1 Answers

You may need to bind the text like this:

 struct DisplayView: UIViewControllerRepresentable  {
 typealias UIViewControllerType = CameraViewController
  @Binding var text: String
 ....
like image 142
E.Coms Avatar answered Nov 04 '25 05:11

E.Coms



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!