Working in Swift: How to transfer variables between UIKit (ViewRepresentabble) and SwiftUI

Viewed 214

I am new to swift and I am having treouble making my SwiftUI and UIkit pices of code talk to each other.

I have a variable in my swiftUI that I want to transfer to the UIkit piece or vice versa.

This is my swfitUI code and I want to pass someString to the UIKit code

struct ForceTestView: View {
//MARK: - PROPERTIES

//MARK: - BODY
var body: some View {
    
    VStack(spacing: 20){
        
        TouchesSwiftUI()
        .border(Color.gray, width: 5)
        .frame(maxWidth: 200, maxHeight: 200)
        
        Text("\(someString)")
            .onTapGesture {
                someString = "New Value"
            }

        
    }//:VSTACK

  
    
}//: BODY


struct TouchesSwiftUI: UIViewControllerRepresentable {
  
    func makeUIViewController(context: Context) -> TouchesHelper {
        let touchControl = TouchesHelper()
        
         return touchControl
        
    }
    func updateUIViewController(_ uiViewController: TouchesHelper, context: Context) {
          
    }
    

}

This is the UIkit code that I have

import Foundation
import UIKit
import SwiftUI
import Combine

class TouchesHelper: UIViewController{
    
    var forceLabel: UILabel!
    var forceText: String = "frcTxt Empty"
    
    var xPosLabel: UILabel!
    
    
    
    override func loadView() {
        view = UIView()
        view.backgroundColor = .white
        
        forceLabel = UILabel()
        forceLabel.translatesAutoresizingMaskIntoConstraints = false
        forceLabel.textAlignment = .left
        forceLabel.font = UIFont.systemFont(ofSize: 18)
        forceLabel.text = "Force Readings"
        forceLabel.numberOfLines = 0
        view.addSubview(forceLabel)
        
        xPosLabel = UILabel()
        xPosLabel.translatesAutoresizingMaskIntoConstraints = false
        xPosLabel.textAlignment = .left
        xPosLabel.font = UIFont.systemFont(ofSize: 18)
        xPosLabel.text = "Finger X Position"
        xPosLabel.numberOfLines = 0
        view.addSubview(xPosLabel)
        
        NSLayoutConstraint.activate([
            forceLabel.topAnchor.constraint(equalTo: view.layoutMarginsGuide.topAnchor),
            forceLabel.leadingAnchor.constraint(equalTo: view.layoutMarginsGuide.leadingAnchor),
            
            xPosLabel.topAnchor.constraint(equalTo: forceLabel.bottomAnchor),
            xPosLabel.leadingAnchor.constraint(equalTo: view.layoutMarginsGuide.leadingAnchor),

        ])
    }
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
    }
    
    override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
        if let touch = touches.first{
            let userForce = touch.force
            forceLabel.text = "Force: \(userForce)"
            forceText = "\(userForce)"
            print("UI \(testString)")
        }//:if touch
        
    }//: touchesMoved

}

I would want to pass the userForce to swiftUI, and then update something like the userxPosLabel with the text input from SwiftUI

I have tried to look at @Obverbable objects and coordinators, but they don't really make sense or work because they don't help in passing things that are connected to touching events within TOuchesMoved (I am coding this for a project that uses an iPhoneX, which had 3D touch)

Any help will be really appreciated!

Thank you!

1 Answers

There are a couple of different ways you can approach the specifics of this, but your inclination to use an ObservableObject like you mentioned is a good one.

Code first, and then some explanation:


class ViewModel : ObservableObject {
    @Published var force : String = ""
    @Published var xPos : String = ""
}

struct ContentView : View {
    @StateObject var viewModel = ViewModel()
    
    //MARK: - BODY
    var body: some View {
        VStack(spacing: 20){
            TouchesSwiftUI(viewModel: viewModel, force: viewModel.force, xPos: viewModel.xPos)
                .border(Color.gray, width: 5)
                .frame(maxWidth: 200, maxHeight: 200)
            Text(viewModel.force)
                .onTapGesture {
                    viewModel.force = "New Value"
                }
            Text(viewModel.xPos)
        }//:VSTACK
    }//: BODY
}


struct TouchesSwiftUI: UIViewControllerRepresentable {
    var viewModel : ViewModel
    var force: String
    var xPos: String
    
    func makeUIViewController(context: Context) -> TouchesHelper {
        let touchControl = TouchesHelper()
        return touchControl
    }
    func updateUIViewController(_ uiViewController: TouchesHelper, context: Context) {
        uiViewController.viewModel = viewModel
        uiViewController.forceLabel.text = force
        uiViewController.xPosLabel.text = xPos
    }
}

class TouchesHelper: UIViewController{
    var viewModel : ViewModel?
    //    {
    //        didSet {
    //            guard let viewModel = viewModel else { return }
    //            cancellable = viewModel.objectWillChange.sink(receiveValue: { _ in
    //                DispatchQueue.main.async {
    //                    forceLabel.text = viewModel.force
    //                    xPosLabel.text = viewModel.
    //                }
    //            })
    //        }
    //    }
    var forceLabel: UILabel!
    var xPosLabel: UILabel!
    
    private var cancellable : AnyCancellable?
    
    override func loadView() {
        view = UIView()
        view.backgroundColor = .white
        
        forceLabel = UILabel()
        forceLabel.translatesAutoresizingMaskIntoConstraints = false
        forceLabel.textAlignment = .left
        forceLabel.font = UIFont.systemFont(ofSize: 18)
        forceLabel.text = "Force Readings"
        forceLabel.numberOfLines = 0
        view.addSubview(forceLabel)
        
        xPosLabel = UILabel()
        xPosLabel.translatesAutoresizingMaskIntoConstraints = false
        xPosLabel.textAlignment = .left
        xPosLabel.font = UIFont.systemFont(ofSize: 18)
        xPosLabel.text = "Finger X Position"
        xPosLabel.numberOfLines = 0
        view.addSubview(xPosLabel)
        
        NSLayoutConstraint.activate([
            forceLabel.topAnchor.constraint(equalTo: view.layoutMarginsGuide.topAnchor),
            forceLabel.leadingAnchor.constraint(equalTo: view.layoutMarginsGuide.leadingAnchor),
            
            xPosLabel.topAnchor.constraint(equalTo: forceLabel.bottomAnchor),
            xPosLabel.leadingAnchor.constraint(equalTo: view.layoutMarginsGuide.leadingAnchor),
            
        ])
    }
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
    }
    
    override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
        if let touch = touches.first{
            let userForce = touch.force
            //forceLabel.text = "Force: \(userForce)"
            //xPosLabel.text = "X: \(touch.location(in: self.view).x)"
            
            viewModel?.force = "\(userForce)"
            viewModel?.xPos = "X: \(Int(touch.location(in: self.view).x))"
        }//:if touch
    }//: touchesMoved
}

What's happening:

  1. ViewModel holds a shared state. This controls what's on the SwiftUI Text labels on ContentView and gets passed to the TouchesSwiftUI which in turn passes it to TouchesHelper

  2. In touchesMoved, TouchesHelper updates ViewModel -- the changes in the labels end up getting propagated back through to TouchesHelper updateUIViewController

  3. You can see that your onTapGesture affects the state as you would expect in both ContentView and TouchesHelper

  4. I left a bit commented in TouchesHelper -- this is just to show that if you wanted to subscribe to updates via publishers on the ViewModel you could do that to change the label text rather than passing then back through updateUIViewController

Related