2

I am using a FluentUI#button, which behind uses UIKit

I need to display that button in a SwiftUI View, and I'm trying to toggle an @State property or add a #selector to the button, but I'm not able to do it

I created a generic UIViewRepresentable structure to help me embed any UIView in my SwiftUI Views, following this tutorial:

struct Anything<Wrapper : UIView>: UIViewRepresentable {
    typealias Updater = (Wrapper, Context) -> Void

    var makeView: () -> Wrapper
    var update: (Wrapper, Context) -> Void

    init(_ makeView: @escaping @autoclosure () -> Wrapper,
         updater update: @escaping (Wrapper) -> Void) {
        self.makeView = makeView
        self.update = { view, _ in update(view) }
    }

    func makeUIView(context: Context) -> Wrapper {
        makeView()
    }

    func updateUIView(_ view: Wrapper, context: Context) {
        update(view, context)
    }
}

And I have the following code:

import SwiftUI
import FluentUI

struct MyView: View {
    @State var isGreen = true
    
    var body: some View {
        VStack {
            Text("Hello, World!")
                .background(isGreen ? Color.green : Color.blue)
            Spacer().frame(height: 20)
            Anything(FluentUI.Button(style: .primaryFilled)) {
                $0.setTitle("Try me!", for: .normal)
            }
            .frame(height: 30)
            .padding()
        }
    }
}

struct Anything<Wrapper: UIView>: UIViewRepresentable {
    typealias Updater = (Wrapper, Context) -> Void

    var makeView: () -> Wrapper
    var update: (Wrapper, Context) -> Void
    var action: (() -> Void)?

    init(_ makeView: @escaping @autoclosure () -> Wrapper,
         updater update: @escaping (Wrapper) -> Void) {
        self.makeView = makeView
        self.update = { view, _ in update(view) }
    }

    func makeUIView(context: Context) -> Wrapper {
        makeView()
    }

    func updateUIView(_ view: Wrapper, context: Context) {
        update(view, context)
    }
}

struct SwiftUIView_Previews: PreviewProvider {
    static var previews: some View {
        MyView()
    }
}

And if I try to add this:

$0.addTarget(self, action: #selector(toggleColor), for: .touchUpInside)

With:

func toggleColor() {
    isGreen = !isGreen
}

I get this error:

Argument of '#selector' refers to instance method 'toggleColor()' that is not exposed to Objective-C

And if I add @objc to the method I get this error:

@objc can only be used with members of classes, @objc protocols, and concrete extensions of classes

And as my Anything struct isn't a Button from SwiftUI, I cannot add the action parameter as normally

How can I add a target/action to my button in this way?

2
  • I think that this issue is part of why there's a Coordinator pattern available. You could make an @objc-exposed function on your coordinator and then forward messages from there to your View using a closure. But, it'll be (at least) one more step compared to what you have. Commented May 3, 2022 at 19:59
  • Let me have a try @jnpdx, I appreciate your insights Commented May 3, 2022 at 20:00

1 Answer 1

2

Here is a demo of possible solution - we need a wrapper between UIKit objective-c selectors and SwiftUI swift function.

Tested with Xcode 13.3 / iOS 15.4

demo

Here is main part (used UIButton instead of FluentUI.Button for simplicity):

Anything(UIButton(type: .system)) {
    $0.setTitle("Try me!", for: .normal)

    $0.addTarget(toggleColor, action: #selector(Action.perform(sender:)), for: .touchUpInside)
    toggleColor.action = {
        isGreen.toggle()
    }
}

Complete test module is here

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.