我希望能够将对UIViewRespresentable
(或者也许是Coordinator
)上方法的引用传递给父级View
。我能想到的唯一方法是在父View
结构上创建一个带有一个类的字段,然后将该类传递给子对象,该子对象充当此行为的委托。但这看起来很冗长。
这里的用例是能够从标准SwiftUI Button
调用一个方法,该方法将缩放MKMapView
中当前位置,而UIViewRepresentable
埋在其他地方的Binding
中那个树。我不希望当前位置为UIViewRepresentable
,因为我希望此操作为一次性操作且不会在用户界面中不断反映。
TL; DR 是否有一种标准的方法,至少在ctrl+c
s中,使父母在SwiftUI中获得对孩子的引用? (我知道这在大多数情况下可能是不希望的,并且很大程度上违反了SwiftUI模式。)
答案 0 :(得分:1)
我自己也为此苦苦挣扎,以下是使用 Combine
和 PassthroughSubject
的方法:
struct OuterView: View {
private var didChange = PassthroughSubject<String, Never>()
var body: some View {
VStack {
// send the PassthroughSubject over
Wrapper(didChange: didChange)
Button(action: {
self.didChange.send("customString")
})
}
}
}
// This is representable struct that acts as the bridge between UIKit <> SwiftUI
struct Wrapper: UIViewRepresentable {
var didChange: PassthroughSubject<String, Never>
@State var cancellable: AnyCancellable? = nil
func makeUIView(context: Context) → SomeView {
let someView = SomeView()
// ... perform some initializations here
// doing it in `main` thread is required to avoid the state being modified during
// a view update
DispatchQueue.main.async {
// very important to capture it as a variable, otherwise it'll be short lived.
self.cancellable = didChange.sink { (value) in
print("Received: \(value)")
// here you can do a switch case to know which method to call
// on your UIKit class, example:
if (value == "customString") {
// call your function!
someView.customFunction()
}
}
}
return someView
}
}
// This is your usual UIKit View
class SomeView: UIView {
func customFunction() {
// ...
}
}
答案 1 :(得分:0)
我确信还有更好的方法,包括使用Combine
和PassthroughSubject
。 (但是我从来没有这么做过。)也就是说,如果您愿意“违背SwiftUI模式”,为什么不发送Notification
呢? (我就是这样做的。)
在我的模型中:
extension Notification.Name {
static let executeUIKitFunction = Notification.Name("ExecuteUIKitFunction")
}
final class Model : ObservableObject {
@Published var executeFuntionInUIKit = false {
willSet {
NotificationCenter.default.post(name: .executeUIKitFunction, object: nil, userInfo: nil)
}
}
}
在我的UIKit中代表:
NotificationCenter.default.addObserver(self, selector: #selector(myUIKitFunction), name: .executeUIKitFunction, object: nil)
根据可表示的类型,将其放置在init
或viewDidLoad
中。
同样,这个不是“纯粹的” SwiftUI或Combine,但是比我更好的人可能会给您-您听起来很乐意得到一些有用的东西。相信我,这行得通。
编辑:值得注意的是,您无需在可表示的内容中做任何额外的事-这仅适用于模型与UIKit视图或视图控制器之间。
答案 2 :(得分:0)
我来这里是为了找到一个更好的答案,然后是我自己想出的一个答案,但这也许确实对某人有所帮助?
尽管如此,它仍然很冗长,并且感觉不太像是最惯用的解决方案,因此可能与问题的作者所寻找的不完全相同。但是,它确实避免了污染全局名称空间,并允许同步(和重复)执行并返回值,这与之前发布的基于NotificationCenter
的解决方案不同。
考虑使用的替代方法是使用@StateObject
,但是我目前需要支持iOS 13,但尚不可用。
游览:我为什么要那样?我需要处理一个触摸事件,但是我正在与SwiftUI世界中定义的另一个手势竞争,该手势将优先于我的UITapGestureRecognizer
。 (我希望这能为下面的简短示例代码提供一些上下文。)
所以我想到的是:
FooView
上),BarViewRepresentable
)中,makeUIView
填写BazUIView
上调用方法。注意:它会导致BarViewRepresentable
的不必要的/不必要的后续更新,因为设置绑定会更改可表示视图的状态,但这对我而言并不是真正的问题。
struct FooView: View {
@State private var closure: ((CGPoint) -> ())?
var body: some View {
BarViewRepresentable(closure: $closure)
.dragGesture(
DragGesture(minimumDistance: 0, coordinateSpace: .local)
.onEnded { value in
self.closure?(value.location)
})
)
}
}
class BarViewRepresentable: UIViewRepresentable {
@Binding var closure: ((CGPoint) -> ())?
func makeUIView(context: UIViewRepresentableContext<BarViewRepresentable>) -> BazUIView {
let view = BazUIView(frame: .zero)
updateUIView(view: view, context: context)
return view
}
func updateUIView(view: BazUIView, context: UIViewRepresentableContext<BarViewRepresentable>) {
DispatchQueue.main.async { [weak self] in
guard let strongSelf = self else { return }
strongSelf.closure = { [weak view] point in
guard let strongView = view? else {
return
}
strongView.handleTap(at: point)
}
}
}
}
class BazUIView: UIView { /*...*/ }