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.
CodePudding user response:
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.