I'm trying to find a way to trigger an action that will call a function in my UIView
when a button gets tapped inside swiftUI
.
Here's my setup:
foo()(UIView)
needs to run when Button(SwiftUI)
gets tapped
class SomeView: UIView {
func foo() {}
}
UIViewRepresentable
struct SomeViewRepresentable: UIViewRepresentable {
func makeUIView(context: Context) -> CaptureView {
SomeView()
}
func updateUIView(_ uiView: CaptureView, context: Context) {
}
}
struct ContentView : View {
var body: some View {
VStack(alignment: .center, spacing: 24) {
SomeViewRepresentable()
.background(Color.gray)
HStack {
Button(action: {
print("SwiftUI: Button tapped")
// Call func in SomeView()
}) {
Text("Tap Here")
}
}
}
}
}
Here's another way to do it using a bridging class.
//SwiftUI
struct SomeView: View{
var bridge: BridgeStuff?
var body: some View{
Button("Click Me"){
bridge?.yo()
}
}
}
//UIKit or AppKit (use NS instead of UI)
class BridgeStuff{
var yo:() -> Void = {}
}
class YourViewController: UIViewController{
override func viewDidLoad(){
let bridge = BridgeStuff()
let view = UIHostingController(rootView: SomeView(bridge: bridge))
bridge.yo = { [weak self] in
print("Yo")
self?.howdy()
}
}
func howdy(){
print("Howdy")
}
}
You can store an instance of your custom UIView
in your representable struct (SomeViewRepresentable
here) and call its methods on tap actions:
struct SomeViewRepresentable: UIViewRepresentable {
let someView = SomeView() // add this instance
func makeUIView(context: Context) -> SomeView { // changed your CaptureView to SomeView to make it compile
someView
}
func updateUIView(_ uiView: SomeView, context: Context) {
}
func callFoo() {
someView.foo()
}
}
And your view body will look like this:
let someView = SomeViewRepresentable()
var body: some View {
VStack(alignment: .center, spacing: 24) {
someView
.background(Color.gray)
HStack {
Button(action: {
print("SwiftUI: Button tapped")
// Call func in SomeView()
self.someView.callFoo()
}) {
Text("Tap Here")
}
}
}
}
To test it I added a print to the foo()
method:
class SomeView: UIView {
func foo() {
print("foo called!")
}
}
Now tapping on your button will trigger foo()
and the print statement will be shown.
M Reza's solution works for simple situations, however if your parent SwiftUI view has state changes, every time when it refreshes, it will cause your UIViewRepresentable to create new instance of UIView because of this: let someView = SomeView() // add this instance
. Therefore someView.foo()
is calling the action on the previous instance of SomeView
you created, which is already outdated upon refreshing, so you might not see any updates of your UIViewRepresentable appear on your parent view.
See: https://medium.com/zendesk-engineering/swiftui-uiview-a-simple-mistake-b794bd8c5678
A better practice would be to avoid creating and referencing that instance of UIView when calling its function.
My adaption to M Reza's solution would be calling the function indirectly through parent view's state change, which triggers updateUIView
:
var body: some View {
@State var buttonPressed: Bool = false
VStack(alignment: .center, spacing: 24) {
//pass in the @State variable which triggers actions in updateUIVIew
SomeViewRepresentable(buttonPressed: $buttonPressed)
.background(Color.gray)
HStack {
Button(action: {
buttonPressed = true
}) {
Text("Tap Here")
}
}
}
}
struct SomeViewRepresentable: UIViewRepresentable {
@Binding var buttonPressed: Bool
func makeUIView(context: Context) -> SomeView {
return SomeView()
}
//called every time buttonPressed is updated
func updateUIView(_ uiView: SomeView, context: Context) {
if buttonPressed {
//called on that instance of SomeView that you see in the parent view
uiView.foo()
buttonPressed = false
}
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With