dot3
dot3

Reputation: 1216

SwiftUI = UIViewControllerRepresentable Update Text via @Binding

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:

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.

Upvotes: 4

Views: 8620

Answers (1)

E.Coms
E.Coms

Reputation: 11531

You may need to bind the text like this:

 struct DisplayView: UIViewControllerRepresentable  {
 typealias UIViewControllerType = CameraViewController
  @Binding var text: String
 ....

Upvotes: 3

Related Questions