值更改时如何在 UIKit 中更新 SwiftUI 视图?
Posted
技术标签:
【中文标题】值更改时如何在 UIKit 中更新 SwiftUI 视图?【英文标题】:How do I update a SwiftUI View in UIKit when value changes? 【发布时间】:2020-02-06 00:19:30 【问题描述】:我正在尝试将一个 SwiftUI 视图集成到一个现有的 UIKit 应用程序中,该视图在更改 @State 变量时进行动画处理(最初的进度是 @State 私有进度:SwiftUI 视图中的 CGFloat = 0.5) .我已经阅读了大量关于将 SwiftUI 集成到 UIKit(@State、@Binding、@Environment 等)的搜索结果,但似乎无法弄清楚这一点。
我创建了一个非常简单的示例来说明我正在尝试做的事情,因为我相信一旦我看到这个答案,我就可以将它应用到我现有的应用程序中。
Storyboard 只是带有 UISlider 的视图控制器。下面的代码显示了 SwiftUI 视图,但我希望它在我移动 UISlider 时更新。
import SwiftUI
class ViewController: UIViewController
var progress: CGFloat = 0.5
override func viewDidLoad()
super.viewDidLoad()
// Do any additional setup after loading the view.
let frame = CGRect(x: 20, y: 200, width: 400, height: 400)
let childView = UIHostingController(rootView: Animate_Trim(progress: progress))
addChild(childView)
childView.view.frame = frame
view.addSubview(childView.view)
childView.didMove(toParent: self)
@IBAction func sliderAction(_ sender: UISlider)
progress = CGFloat(sender.value)
print("Progress: \(progress)")
struct Animate_Trim: View
var progress: CGFloat
var body: some View
VStack(spacing: 20)
Circle()
.trim(from: 0, to: progress) // Animate trim
.stroke(Color.blue,
style: StrokeStyle(lineWidth: 40,
lineCap: CGLineCap.round))
.frame(height: 300)
.rotationEffect(.degrees(-90)) // Start from top
.padding(40)
.animation(.default)
Spacer()
.font(.title)
```
【问题讨论】:
【参考方案1】:接受的答案实际上并没有回答原始问题“在 UIKit 中更新 SwiftUI 视图...”?
恕我直言,当您想与 UIKit 交互时,您可以使用通知来更新进度视图:
extension Notification.Name
static var progress: Notification.Name return .init("progress")
class ViewController: UIViewController
var progress: CGFloat = 0.5
didSet
let userinfo: [String: CGFloat] = ["progress": self.progress]
NotificationCenter.default.post(Notification(name: .progress,
object: nil,
userInfo: userinfo))
var slider: UISlider = UISlider()
override func viewDidLoad()
super.viewDidLoad()
slider.addTarget(self, action: #selector(sliderAction(_:)), for: .valueChanged)
slider.frame = CGRect(x: 0, y: 500, width: 200, height: 50)
// Do any additional setup after loading the view.
let frame = CGRect(x: 20, y: 200, width: 400, height: 400)
let childView = UIHostingController(rootView: Animate_Trim())
addChild(childView)
childView.view.frame = frame
view.addSubview(childView.view)
view.addSubview(slider)
childView.didMove(toParent: self)
@IBAction func sliderAction(_ sender: UISlider)
progress = CGFloat(sender.value)
print("Progress: \(progress)")
struct Animate_Trim: View
@State var progress: CGFloat = 0
var notificationChanged = NotificationCenter.default.publisher(for: .progress)
var body: some View
VStack(spacing: 20)
Circle()
.trim(from: 0, to: progress) // Animate trim
.stroke(Color.blue,
style: StrokeStyle(lineWidth: 40,
lineCap: CGLineCap.round))
.frame(height: 300)
.rotationEffect(.degrees(-90)) // Start from top
.padding(40)
.animation(.default)
.onReceive(notificationChanged) note in
self.progress = note.userInfo!["progress"]! as! CGFloat
Spacer()
.font(.title)
【讨论】:
这看起来有点矫枉过正,没有更简单的方法可以从 UIKit 更新 SwiftUI 中的 @Binding 变量吗?【参考方案2】:如果您不想使用通知中心。您可以只使用 @Published 并分配或接收。
我在 Playground 中编写了一个工作示例来展示这个概念:
//This code runs on Xcode playground
import Combine
import SwiftUI
class ObservableSlider: ObservableObject
@Published public var value: Double = 0.0
class YourViewController
var observableSlider:ObservableSlider = ObservableSlider()
private var cancellables: Set<AnyCancellable> = []
let hostController = YourHostingController() // I put it here for the sake of the example, but you do need a reference to the Hosting Controller.
init() // In a real VC this code would probably be on viewDidLoad
let swiftUIView = hostController.rootView
//This is where values of SwiftUI view and UIKit get glued together
self.observableSlider.$value.assign(to: \.observableSlider.value, on: swiftUIView).store(in:&self.cancellables)
func updateSlider()
observableSlider.value = 8.5
// In real app it would be something like:
//class YourHostingController<YourSwiftUIView> UIHostingController
class YourHostingController
var rootView = YourSwiftUIView()
//In a real Hosting controller you would do something like:
// required init?(coder aDecoder: NSCoder)
// super.init(coder: aDecoder, rootView: YourSwiftUIView())
//
struct YourSwiftUIView: View
var body: some View
EmptyView() // Your real SwiftUI body...
@ObservedObject var observableSlider: ObservableSlider = ObservableSlider()
func showValue()
print(observableSlider.value)
init()
print(observableSlider.value)
let yourVC = YourViewController() // Inits view and prints 0.0
yourVC.updateSlider() // Updates from UIKit to 8.5
yourVC.hostController.rootView.showValue() // Value in SwiftUI View is updated (prints 8.5)
【讨论】:
【参考方案3】:Combine 是你的朋友......
-
创建模型
从 UISlider 或应用程序的任何其他部分更新模型
使用模型更新您的 SwiftUI.View
我做了简单的例子,只使用 SwiftUI,但使用相同的场景
import SwiftUI
class Model: ObservableObject
@Published var progress: Double = 0.2
struct SliderView: View
@EnvironmentObject var slidermodel: Model
var body: some View
// this is not part of state of the View !!!
// the bindig is created directly to your global EnnvironmentObject
// be sure that it is available by
// creating the SwiftUI view that provides the window contents
// in your SceneDelegate.scene(....)
// let model = Model()
// let contentView = ContentView().environmentObject(model)
let binding = Binding<Double>(get: () -> Double in
self.slidermodel.progress
) (value) in
self.slidermodel.progress = value
return Slider(value: binding, in: 0.0 ... 1.0)
struct ContentView: View
@EnvironmentObject var model: Model
var body: some View
VStack
Circle()
.trim(from: 0, to: CGFloat(model.progress)) // Animate trim
.stroke(Color.blue,
style: StrokeStyle(lineWidth: 40,
lineCap: CGLineCap.round))
.frame(height: 300)
.rotationEffect(.degrees(-90)) // Start from top
.padding(40)
.animation(.default)
SliderView()
struct ContentView_Previews: PreviewProvider
static var previews: some View
ContentView().environmentObject(Model())
一切都很好地协同工作
【讨论】:
这很酷,但没有直接回答关于从 UIKit 更新 SwiftUI 的 OP 如前所述,这根本不使用 UIKit。以上是关于值更改时如何在 UIKit 中更新 SwiftUI 视图?的主要内容,如果未能解决你的问题,请参考以下文章
SwiftUI 视图,如 UIKit UIView,如何更改图像的大小
当 UserDefaults 值更改 SwiftUI 时,@FetchRequest 谓词不会更新
在 SwiftUI 中通过摇动手势(使用 UIKit)设置 EnvironmentObject 不会更新视图