Работа в Swift: как передавать переменные между UIKit (ViewRepresentabble) и SwiftUI

#swift #swiftui #uikit

#быстрый #свифтуи #uikit

Вопрос:

Я новичок в swift, и у меня возникает проблема, заставляющая мои части кода SwiftUI и UIKit взаимодействовать друг с другом.

У меня есть переменная в моем SwiftUI, которую я хочу перенести в часть UIKit или наоборот.

Это мой код swfitUI, и я хочу перейти someString к коду UIKit

 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) {
          
    }
    

}
 

Это код UIKit, который у меня есть

 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

}
 

Я хотел бы передать userForce в SwiftUI, а затем обновить что-то вроде userxPosLabel с помощью ввода текста из SwiftUI

Я попытался взглянуть на объекты и координаторов @Obverbable, но они на самом деле не имеют смысла или не работают, потому что они не помогают передавать вещи, связанные с событиями касания в touchesMoved (я кодирую это для проекта, который использует iPhoneX, у которого был 3D touch)

Любая помощь будет очень признательна!

Спасибо!

Ответ №1:

Есть несколько разных способов, которыми вы можете подойти к специфике этого, но ваша склонность использовать ObservableObject то, что вы упомянули, является хорошей.

Сначала код, а затем некоторые пояснения:

 
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
}

 

Что происходит:

  1. ViewModel содержит общее состояние. Это управляет тем, что Text находится на ярлыках SwiftUI, ContentView и передается на TouchesSwiftUI который, в свою очередь, передает его TouchesHelper
  2. В touchesMoved , TouchesHelper обновления ViewModel — изменения в метках в конечном итоге распространяются обратно на TouchesHelper updateUIViewController
  3. Вы можете видеть, что ваш onTapGesture влияет на состояние, как и следовало ожидать ContentView , как в TouchesHelper
  4. Я оставил немного комментариев в TouchesHelper — это просто для того, чтобы показать, что если вы хотите подписаться на обновления через издателей ViewModel , вы можете сделать это, чтобы изменить текст метки, а не передавать ее обратно updateUIViewController

Комментарии:

1. Большое спасибо mcuh за подробный код и объяснение, вы даже не представляете, как это помогло! Я потратил три дня на поиски решения, а ты сделал это за один день! Опять же, я действительно ценю вашу помощь, вы не знаете, что это значит для меня 🙂

2. Фантастика. Рад, что это помогло. Поскольку это было полезно, вы также можете щелкнуть стрелку вверх рядом с ответом, чтобы «проголосовать» за него в дополнение к его принятию.

3. Да! извините, все еще новичок на форуме, да, я только что поддержал ответ и ваш комментарий, надеюсь, он показывает, что он не может быть общедоступным, потому что я новый пользователь, но я проверю через пару дней и посмотрю, прошло ли это!