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 theUIViewController
and communicate with itCombine 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:
VCLink
is anObservableObject
that I'm using as a go-between to communicate between views- The
ContentView
has a reference to theVCLink
-- when the button is pressed, thePublisher
onVCLink
communicates that to any subscribers - When the
VCRepresented
is created/updated, I store a reference to the ViewController and theVCLink
in itsCoordinator
- The
Coordinator
takes thePublisher
and in itssink
method, performs an action on the storedViewController
. 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
In Collectionview How to Set Colors According to Selection
My Uiviews Muck-Up When I Combine Uipangesturerecognizer and Autolayout
Returning Data from Function in Firebase Observer Code Block Swift
User Notification Request Always Come with Default Action Identifier
Swift 2 Unused Constant Warning
Get Progress of Uinavigationcontroller Swipe Back
Nsubiquityidentitydidchangenotification and Sigkill
Swift - Uipopovercontroller in iOS 8
Passing Data Between View Controllers Without Segue
How to Programmatically Check and Open an Existing App in iOS 8
iOS Swift3 Check Nil Value for Viewcontroller Object
What Are the First Two Columns in Scnmatrix4
iOS Keyboard Active But Invisible When Uisearchbar Is Tapped
Objective-C Call Swift Function
Struggling to Convert Objective C Selector and Target Signature to Swift