How to Execute a Function in a Uiviewcontroller Through a Swiftui Button

How to execute a function in a UIViewController through a SwiftUI Button?

Hello to control the UIViewController we need to create a bind between the View and UIViewControllerRepresentable
let explain it in code:
first you need to declare a new variable annotated with @Binding inside the AnotherControllerView

it will be like this :

struct AnotherControllerView : UIViewControllerRepresentable {

@Binding var isShown: Bool
typealias UIViewControllerType = AnotherController

func makeCoordinator() -> AnotherControllerView.Coordinator {
Coordinator()
}

func makeUIViewController(context: UIViewControllerRepresentableContext<AnotherControllerView>) -> AnotherController {

return AnotherController(isShown: $isShown)
}

func updateUIViewController(_ controller: AnotherController, context: UIViewControllerRepresentableContext<AnotherControllerView>) {
if(self.isShown){
controller.savePhoto()
}
}

class Coordinator : NSObject {

}
}

so for that in the updateUIViewController we implement the logic there

class AnotherController : UIViewController {

@Binding var isShown: Bool

init(isShown: Binding<Bool>) {
_isShown = isShown
super.init(nibName: nil, bundle: nil)

}

required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}

override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = UIColor.blue
}

func savePhoto(){

let alert = UIAlertController(title: "Save Photo to Camera Roll", message: "Would you like to save your drawing to the camera roll?", preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "Cancel", style: .default, handler: cancelAlert))
alert.addAction(UIAlertAction(title: "Save", style: .default, handler: someHandler))
self.present(alert, animated: true)
}

func cancelAlert(alert: UIAlertAction!) {
self.isShown = false
}

func someHandler(alert: UIAlertAction!) {
print("Handler executed")
}
}

and if you're trying to capture an image let me show you how I implemented this:

struct CaptureImageView {
@Binding var isShown: Bool
@Binding var image: Image?
var sourceType: UIImagePickerController.SourceType

func makeCoordinator() -> Coordinator {
return Coordinator(isShown: $isShown, image: $image)
}
}

extension CaptureImageView: UIViewControllerRepresentable {
func makeUIViewController(context: UIViewControllerRepresentableContext<CaptureImageView>) -> UIImagePickerController {
let picker = UIImagePickerController()
picker.delegate = context.coordinator
picker.sourceType = sourceType
return picker
}

func updateUIViewController(_ uiViewController: UIImagePickerController, context: UIViewControllerRepresentableContext<CaptureImageView>) {

}

class Coordinator: NSObject, UINavigationControllerDelegate, UIImagePickerControllerDelegate {
@Binding var isCoordindatorShown: Bool
@Binding var imageInCoordinator: Image?

init(isShown: Binding<Bool>, image: Binding<Image?>) {
_isCoordindatorShown = isShown
_imageInCoordinator = image
}

func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
guard let unwrapImage = info[UIImagePickerController.InfoKey.originalImage] as? UIImage else {return}
imageInCoordinator = Image(uiImage: unwrapImage)
isCoordindatorShown = false
}

func imagePickerControllerDidCancel(_ picker: UIImagePickerController) {
isCoordindatorShown = false
}
}

}

and in the body I just call :

CaptureImageView(isShown: $showCaptureImageView, image: $imageCaptured, sourceType: importSourceType)

Calling functions from UIViewController in SwiftUI

There are probably multiple solutions to this problem, but one way or another, you'll need to find a way to keep a reference to or communicate with the UIViewController. Because SwiftUI views themselves are pretty transient, you can't just store a reference in the view itself, because it could get recreated at any time.

Tools to use:

  • ObservableObject -- this will let you store data in a class instead of a struct and will make it easier to store references, connect data, etc

  • Coordinator -- in a UIViewRepresentable, you can use a Coordinator pattern which will allow you to store references to the UIViewController and communicate with it

  • Combine Publishers -- these are totally optional, but I've chosen to use them here since they're an easy way to move data around without too much boilerplate code.

import SwiftUI
import Combine

struct ContentView: View {
@ObservedObject var vcLink = VCLink()
var body: some View {
VStack {
VCRepresented(vcLink: vcLink)
Button("Take photo") {
vcLink.takePhoto()
}
}
}
}

enum LinkAction {
case takePhoto
}

class VCLink : ObservableObject {
@Published var action : LinkAction?

func takePhoto() {
action = .takePhoto
}
}

class CustomVC : UIViewController {
func action(_ action : LinkAction) {
print("\(action)")
}
}

struct VCRepresented : UIViewControllerRepresentable {
var vcLink : VCLink

class Coordinator {
var vcLink : VCLink? {
didSet {
cancelable = vcLink?.$action.sink(receiveValue: { (action) in
guard let action = action else {
return
}
self.viewController?.action(action)
})
}
}
var viewController : CustomVC?

private var cancelable : AnyCancellable?
}

func makeCoordinator() -> Coordinator {
return Coordinator()
}

func makeUIViewController(context: Context) -> CustomVC {
return CustomVC()
}

func updateUIViewController(_ uiViewController: CustomVC, context: Context) {
context.coordinator.viewController = uiViewController
context.coordinator.vcLink = vcLink
}
}

What happens here:

  1. VCLink is an ObservableObject that I'm using as a go-between to communicate between views
  2. The ContentView has a reference to the VCLink -- when the button is pressed, the Publisher on VCLink communicates that to any subscribers
  3. When the VCRepresented is created/updated, I store a reference to the ViewController and the VCLink in its Coordinator
  4. The Coordinator takes the Publisher and in its sink method, performs an action on the stored ViewController. In this demo, I'm just printing the action. In your example, you'd want to trigger the photo itself.

How do I call a function written in UIKit/UIController/Storyboard from SwiftUI button

You can use closures for this. First, define and call them inside your SwiftUI view.

struct SwiftUIView2: View {

var text: String
var startAction: (() -> Void) /// define closure 1
var stopAction: (() -> Void) /// define closure 2

...
...

Button("\(startStop_flag ? "Start": "Stop")") {
startStop_flag = !startStop_flag

if (startStop_flag) {
//******** call StartAction()
startAction()
} else {
//******* call StopAction()
stopAction()
}
}
}

Then, just assign the closure's contents inside ViewController.swift.

@IBSegueAction func embedSwiftUIView(_ coder: NSCoder) -> UIViewController? {
return UIHostingController(
coder: coder,
rootView: SwiftUIView2(
text: "Container View",
startAction: { [weak self] in
self?.startAction()
},
stopAction: { [weak self] in
self?.stopAction()
}
)
)
}

Send tapAction from SwiftUI button action to UIView function

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.

Call UIKIT function from SWIFTUI

Here is a sample

class UserDashboardModel: ObservableObject {
@Published var totalBalance = 0.0
internal var uIViewController: UserDashboardHostVC? = nil

func getAcctBalance(){
if uIViewController != nil{
uIViewController!.getAcctBalance()
}else{
print("view controller is not connected")
}
}
}
struct UserDashboard: View {

@ObservedObject var userModel: UserDashboardModel
var body: some View{
VStack{
Button(action: {
userModel.getAcctBalance() //Call shared model
}){
Text("Load Balance")
}

Text(userModel.totalBalance.description) //Now it will change
}.frame(width: 400, height: 400, alignment: .center)
}
}
class UserDashboardHostVC: UIViewController {

var userModel = UserDashboardModel()

override func viewDidLoad() {
super.viewDidLoad()
//Crucial connection
userModel.uIViewController = self
let controller = UIHostingController(rootView: UserDashboard(userModel: self.userModel))

self.addChild(controller)
self.view.addSubview(controller.view)
controller.view.translatesAutoresizingMaskIntoConstraints = false
//Update constraints per use case
controller.view.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
controller.view.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
controller.didMove(toParent: self)

}
func getAcctBalance() {
userModel.totalBalance = 599
}
}

SwiftUI calling delegate in UIViewController

You simply need to pass a reference to your MyViewController instance through to MyUIView (Which should probably be MyView since it isn't a subclass of UIView) via its initialiser. You could use a delegation pattern or you could pass a closure and then invoke the delegate method from the closure. The second way is more "Swifty".

(I have moved the code to viewDidLoad since if you have it in viewWillAppear the view may be added multiple times if the view controller appears and disappears and appears again)

class MyViewController: UIViewController {

public var delegate: MyViewControllerDelegate? {

weak var myView: UIHostingController<MyUIView>!

override func viewDidLoad() {
super.viewDidLoad()
let myView = UIHostingController(rootView: MyUIView(buttonHandler: { [weak self] in
self?.delegate?.buttonPressed()
}))
self.myView = myView
view.addSubview(myView.view)
setupConstraints()
}
}

Then, in your SwiftUI view you can declare a property to hold the closure and invoke that closure in your button

struct MyUIView: View {

var buttonHandler: (()->Void)?

var body: some View {
VStack() {
Button {
buttonHandler?()
} label: {
Text("Press button")
}

}
}
}

iOS: Make function calls between UIViewRepresentable and View both ways, SwiftUI

You can use computed property and closure for a callback.

Here is the example code.

struct LoginView: View {

// Other code

// Webview var
private var webView: LoginWebview {
LoginWebview(testdo: self.showJSAlert) {
// Here you will get the call back
print("Callback")
}
}

var body: some View {

webView // <-- Here

// Other Code

And for button action

Button(action: {

//Calls login webview and triggers update that calls the js
self.showJSAlert.toggle()
// Access your function
self.webView.printstuff()

}) {
Text("Login")
.padding()
.font(.system(size: 20))

}

And in LoginWebview

struct LoginWebview: UIViewRepresentable {
var testdo = false

var callBack: (() -> Void)? = nil
class Coordinator: NSObject, WKNavigationDelegate, WKScriptMessageHandler {
// Other code

func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
self.control.callBack?() // < Example for calling callback
}

// Other code
}



Related Topics



Leave a reply



Submit