如何绑定 SwiftUI 和 UIViewController 行为
How to bind SwiftUI and UIViewController behavior
我有一个带有 UIViewControllers 的 UIKit 项目,我想展示一个基于我的 ViewController 的 SwiftUI 构建的动作 sheet。我需要将动作 sheet 的出现和消失绑定回视图控制器,使视图控制器能够被关闭(并且显示动画只发生在 viewDidAppear 上,以避免一些奇怪的动画行为发生在使用 .onAppear
)。这是一个代码示例,说明我希望绑定如何工作以及它如何不按我的预期进行:
import UIKit
import SwiftUI
class ViewController: UIViewController {
let button = UIButton(type: .system)
var show = true
lazy var isShowing: Binding<Bool> = .init {
self.show
} set: { show in
// This code gets called
self.show = show
}
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .white
button.setTitle("TAP THIS BUTTON", for: .normal)
view.addSubview(button)
button.translatesAutoresizingMaskIntoConstraints = false
button.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
button.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
button.addTarget(self, action: #selector(tapped), for: .touchUpInside)
}
@objc private func tapped() {
let vc = UIHostingController(rootView: BindingProblemView(testBinding: isShowing))
vc.modalPresentationStyle = .overCurrentContext
present(vc, animated: false)
DispatchQueue.main.asyncAfter(deadline: .now() + 5) { [self] in
isShowing.wrappedValue.toggle()
isShowing.update()
}
}
}
struct BindingProblemView: View {
@Binding var testBinding: Bool
@State var state = "ON"
var body: some View {
ZStack {
if testBinding {
Color.red.ignoresSafeArea().padding(0)
} else {
Color.green.ignoresSafeArea().padding(0)
}
Button("Test Binding is \(state)") {
testBinding.toggle()
}.onChange(of: testBinding, perform: { value in
// This code never gets called
state = testBinding ? "ON" : "OFF"
})
}
}
}
当我设置绑定值 true
时,onChange
从未在 viewDidAppear
之后被调用。我是否完全滥用了新的组合运算符?
您可以通过 ObservableObject
传递数据,而不是通过 Binding
传递数据。这里的想法是 ViewController
具有对 PassedData
实例的引用,该实例被传递给 SwiftUI 视图,该视图接收对对象的更改,因为它是 @ObservedObject
.
现在可以使用了,因此您可以单击原始按钮来显示 SwiftUI 视图。该视图中的按钮然后切换 passedData.isShowing
以更改背景颜色。由于这是一个 class 实例,因此 ViewController
也可以访问该值。例如,isShowing
也会在 5 秒后在 tapped()
内切换,以显示值可以从 ViewController
或 BindingProblemView
更改.
尽管不再需要,onChange(of:perform:)
仍会触发。
代码:
class PassedData: ObservableObject {
@Published var isShowing = true
}
class ViewController: UIViewController {
let button = UIButton(type: .system)
let passedData = PassedData()
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .white
button.setTitle("TAP THIS BUTTON", for: .normal)
view.addSubview(button)
button.translatesAutoresizingMaskIntoConstraints = false
button.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
button.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
button.addTarget(self, action: #selector(tapped), for: .touchUpInside)
}
@objc private func tapped() {
let newView = BindingProblemView(passedData: passedData)
let vc = UIHostingController(rootView: newView)
vc.modalPresentationStyle = .overCurrentContext
present(vc, animated: false)
// Example of toggling from in view controller
DispatchQueue.main.asyncAfter(deadline: .now() + 5) {
self.passedData.isShowing.toggle()
}
}
}
struct BindingProblemView: View {
@ObservedObject var passedData: PassedData
var body: some View {
ZStack {
if passedData.isShowing {
Color.red.ignoresSafeArea().padding(0)
} else {
Color.green.ignoresSafeArea().padding(0)
}
Button("Test Binding is \(passedData.isShowing ? "ON" : "OFF")") {
passedData.isShowing.toggle()
}
}
}
}
结果:
我有一个带有 UIViewControllers 的 UIKit 项目,我想展示一个基于我的 ViewController 的 SwiftUI 构建的动作 sheet。我需要将动作 sheet 的出现和消失绑定回视图控制器,使视图控制器能够被关闭(并且显示动画只发生在 viewDidAppear 上,以避免一些奇怪的动画行为发生在使用 .onAppear
)。这是一个代码示例,说明我希望绑定如何工作以及它如何不按我的预期进行:
import UIKit
import SwiftUI
class ViewController: UIViewController {
let button = UIButton(type: .system)
var show = true
lazy var isShowing: Binding<Bool> = .init {
self.show
} set: { show in
// This code gets called
self.show = show
}
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .white
button.setTitle("TAP THIS BUTTON", for: .normal)
view.addSubview(button)
button.translatesAutoresizingMaskIntoConstraints = false
button.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
button.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
button.addTarget(self, action: #selector(tapped), for: .touchUpInside)
}
@objc private func tapped() {
let vc = UIHostingController(rootView: BindingProblemView(testBinding: isShowing))
vc.modalPresentationStyle = .overCurrentContext
present(vc, animated: false)
DispatchQueue.main.asyncAfter(deadline: .now() + 5) { [self] in
isShowing.wrappedValue.toggle()
isShowing.update()
}
}
}
struct BindingProblemView: View {
@Binding var testBinding: Bool
@State var state = "ON"
var body: some View {
ZStack {
if testBinding {
Color.red.ignoresSafeArea().padding(0)
} else {
Color.green.ignoresSafeArea().padding(0)
}
Button("Test Binding is \(state)") {
testBinding.toggle()
}.onChange(of: testBinding, perform: { value in
// This code never gets called
state = testBinding ? "ON" : "OFF"
})
}
}
}
当我设置绑定值 true
时,onChange
从未在 viewDidAppear
之后被调用。我是否完全滥用了新的组合运算符?
您可以通过 ObservableObject
传递数据,而不是通过 Binding
传递数据。这里的想法是 ViewController
具有对 PassedData
实例的引用,该实例被传递给 SwiftUI 视图,该视图接收对对象的更改,因为它是 @ObservedObject
.
现在可以使用了,因此您可以单击原始按钮来显示 SwiftUI 视图。该视图中的按钮然后切换 passedData.isShowing
以更改背景颜色。由于这是一个 class 实例,因此 ViewController
也可以访问该值。例如,isShowing
也会在 5 秒后在 tapped()
内切换,以显示值可以从 ViewController
或 BindingProblemView
更改.
尽管不再需要,onChange(of:perform:)
仍会触发。
代码:
class PassedData: ObservableObject {
@Published var isShowing = true
}
class ViewController: UIViewController {
let button = UIButton(type: .system)
let passedData = PassedData()
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .white
button.setTitle("TAP THIS BUTTON", for: .normal)
view.addSubview(button)
button.translatesAutoresizingMaskIntoConstraints = false
button.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
button.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
button.addTarget(self, action: #selector(tapped), for: .touchUpInside)
}
@objc private func tapped() {
let newView = BindingProblemView(passedData: passedData)
let vc = UIHostingController(rootView: newView)
vc.modalPresentationStyle = .overCurrentContext
present(vc, animated: false)
// Example of toggling from in view controller
DispatchQueue.main.asyncAfter(deadline: .now() + 5) {
self.passedData.isShowing.toggle()
}
}
}
struct BindingProblemView: View {
@ObservedObject var passedData: PassedData
var body: some View {
ZStack {
if passedData.isShowing {
Color.red.ignoresSafeArea().padding(0)
} else {
Color.green.ignoresSafeArea().padding(0)
}
Button("Test Binding is \(passedData.isShowing ? "ON" : "OFF")") {
passedData.isShowing.toggle()
}
}
}
}
结果: