0

I read this question online and found it to be very interesting. If you have a SwiftUI view as shown below. How can you access the selected rating in a UIKit view without changing a single line in RatingView.

struct RatingView: View {
    
    @Binding var rating: Int?
    
    private func starType(index: Int) -> String {
        
        if let rating = rating {
            return index <= rating ? "star.fill" : "star"
        } else {
            return "star"
        }
    }
    
    var body: some View {
        HStack {
            ForEach(1...5, id: \.self) { index in
                Image(systemName: self.starType(index: index))
                    .foregroundColor(Color.orange)
                    .onTapGesture {
                        rating = index
                }
            }
        }
    }
}

class ViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()
        self.view.backgroundColor = .white
        
        // .constant(3) will be replaced by something in the ViewController so it can handle the changes for the rating
        let hostingController = UIHostingController(rootView: RatingView(rating: .constant(3)))
        
        guard let ratingsView = hostingController.view else { return }
        self.addChild(hostingController)
        
        self.view.addSubview(ratingsView)
}

UPDATE:

Original source: https://twitter.com/azamsharp/status/1540838477599752192?s=20&t=bbDp3VT9m0Ce4W3Sgr7Iyg

I typed most of the code from the original source. I did not change much.

3
  • Binding requires source of truth (if you want two-way update), so the solution will be the same as in stackoverflow.com/a/59247626/12299030. Commented Jun 26, 2022 at 16:24
  • 1
    Btw, would you add in question a reference to the origin of this "online question" and indicate code of your own tries to solve it. Commented Jun 26, 2022 at 16:36
  • @Asperi I have updated the original question. Commented Jun 26, 2022 at 16:41

1 Answer 1

1

We can decorate the RatingView and use an ObservableObject to hold on to the source of truth.

class RatingObserver: ObservableObject {
    @Published var rating: Int?
}

struct WrappedRatingView: View {

    @ObservedObject var ratingObserver: RatingObserver

    var body: some View {
        RatingView(rating: $ratingObserver.rating)
    }
}

Then we can use it in the following way.

class ViewController: UIViewController {

    let ratingObserver = RatingObserver()

    override func viewDidLoad() {
        super.viewDidLoad()
        self.view.backgroundColor = .white

        let hostingController = UIHostingController(
            rootView: WrappedRatingView(ratingObserver: ratingObserver)
        )
        self.addChild(hostingController)
        view.addSubview(hostingController.view)
        hostingController.didMove(toParent: self)
        hostingController.view.translatesAutoresizingMaskIntoConstraints = false

        let button = UIButton()
        button.translatesAutoresizingMaskIntoConstraints = false
        button.setTitle("Reset Rating", for: .normal)
        button.setTitleColor(.blue, for: .normal)
        button.addTarget(self, action: #selector(resetRating), for: .touchUpInside)
        view.addSubview(button)

        NSLayoutConstraint.activate([
            hostingController.view.centerXAnchor.constraint(equalTo: view.centerXAnchor),
            hostingController.view.centerYAnchor.constraint(equalTo: view.centerYAnchor),
            button.centerXAnchor.constraint(equalTo: view.centerXAnchor),
            button.centerYAnchor.constraint(equalTo: view.centerYAnchor, constant: 100)
        ])
    }

    @objc func resetRating() {
        ratingObserver.rating = nil
    }
}

This allows for updating both the ViewController and the SwiftUI view.

Sign up to request clarification or add additional context in comments.

1 Comment

Looks like the original poster of this question on Twitter confirmed that this solution works. Thanks.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.