How to Navigating from SwiftUI View to UIKit UIViewController - swift

As of now, I have an application built entirely using UIKit. However, I wish to be able to start implementing some SwiftUI Views to replace some UIViewControllers.
I've been able to do this to navigate from the UIViewController to SwiftUI View on button tap:
#IBAction func buttonTapped(_ sender: Any) {
let newView = UIHostingController(rootView: SwiftUIView(viewObj: self.view, sb: self.storyboard, dismiss: self.dismiss) )
view.window?.rootViewController = newView
view.window?.makeKeyAndVisible()
}
My question is, how would I transition from a single SwiftUI View to a UIViewController?(Since the rest of the application is in UIKit)? I've got a button in the SwiftUI View, to navigate back to the UIViewController on tap. I've tried:
Passing the view and storyboard objects to the SwiftUI View, then calling doing something similar to the code above to change the current view controller. However, when tried on the simulator nothing happens.
Using .present to show the SwiftUI View modally. This works and I can allow the SwiftUI View to .dismiss itself. However, this only works modally, and I hope to make this work properly (i.e change screen)
Here is my simple SwiftUI View:
struct SwiftUIView: View {
var viewObj:UIView? // Perhaps use this for transition back?
var sb:UIStoryboard?
var dismiss: (() -> Void)?
var body: some View {
Button(action: {
// Do something here to Transition
}) {
Text("This is a SwiftUI view.")
}
}
}
I'm having trouble understanding how to properly integrate SwiftUI into UIKit NOT the other way around, and I'm not sure if UIViewControllerRepresentable is the answer to this. Any solution to this, alternatives or helpful knowledge is very much appreciated. Thanks again!

Ciao,
I tried to follow your approach by using closure callbacks.
struct SwiftUIView: View {
var dismiss: (() -> Void)?
var present: (()->Void)?
var body: some View {
VStack(spacing: 20) {
Button(action: {
self.dismiss?()
}) {
Text("Dismiss me")
}
Button(action: {
self.present?()
}) {
Text("Present some UIViewController")
}
}
}
}
When you present your UIHostingController, you want to implement the 2 closure callbacks:
#IBAction func buttonTapped(_ sender: Any) {
let hostingController = UIHostingController(rootView: SwiftUIView())
hostingController.rootView.dismiss = {
hostingController.dismiss(animated: true, completion: nil)
}
hostingController.rootView.present = {
let destination = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(identifier: "VC_TO_PRESENT")
hostingController.present(destination, animated: true, completion: nil)
}
present(hostingController, animated: true, completion: nil)
}

You can do the same, only in mirror order, like following (scratchy) ...
Button(action: {
if let vc = self.sb?.instantiateViewController(withIdentifier: "some_identifier") {
self.viewObj?.window?.rootViewController = vc
// or via present as alternate
// self.viewObj?.window?.rootViewController.present(vc, animated: true, completion: nil)
}
}) {

you can change the presentation style and transition style to present the controller full screen
let vc = UIHostingController(rootView: LoginView())
vc.modalPresentationStyle = .fullScreen
vc.modalTransitionStyle = .crossDissolve
self.present(vc, animated: true)

Wrap your UIViewController to UIViewControllerRepresentable wrapper.
struct LessonDetailsViewControllerWrapper: UIViewControllerRepresentable {
typealias UIViewControllerType = UIViewController
func makeUIViewController(context: UIViewControllerRepresentableContext<LessonDetailsViewControllerWrapper>) -> UIViewController {
let viewController = LessonDetailsViewController()
return viewController
}
func updateUIViewController(_ uiViewController: UIViewController, context: UIViewControllerRepresentableContext<LessonDetailsViewControllerWrapper>) {}
}
Then from your SwiftUI view Navigate using Navigation Link:
NavigationLink(destination: LessonDetailsViewControllerWrapper()) {
LessonRow(lesson: lesson)
}

Here is the example of push navigation controller from UIKit Button Press
let controller = UIHostingController(rootView: LoginViewController())
self.navigationController?.pushViewController(controller, animated: true)

Related

Pushing a navigation controller is not supported in swift , Move from Modal to Navigation

Similar like iPhone phoneBook, I want make view like VC(tableView) -> Modal -> navigationView. But I'm having some problems:
//vc(tableView)
#objc func addBtnClick(sender: UIButton){
let childVC = InformationView()
let fakeRootView = UINavigationController(rootViewController: childVC)
self.present(fakeRootView, animated: true){
print("rootView CompletionHandler")
}
Then save infomation in InformationView using realm:
#objc
func saveBtnClick(sender: UIButton){
content.phoneName = self.nameField.text!
content.phoneNum = self.phoneNumber.text!
try! realm.write {
realm.add(content, update: .all)
}
print(Realm.Configuration.defaultConfiguration.fileURL!)
table.tableView.reloadData()
self.presentingViewController?.dismiss(animated: true)
navInfoViewLoad()
func navInfoViewLoad(){
let childVC = contentView
self.navigationController?.pushViewController(childVC, animated: true)
}
After pressing saveBtnClick, I want to move to contentView. But I am getting an error:
Thread 1: Pushing a navigation controller is not supported
What is the problem?

How to push controller after dismiss presented controller in swift?

Hey I am showing a controller (A) in main controller with presentation style (not push), and I want to button tapped and push another controller (B) after dismiss this (A) controller, this situation occurred in main controller. I am using protocol for this situation. Any idea for that ? Code like below.`
//this is dismiss button action
var segueDelegate: segueFromController?
#objc func dismissController() {
self.dismiss(animated: true) {
self.segueDelegate?.segueFromController()
}
//and this one is protocol function in main controller
func segueFromController() {
let contProfile = ContViewController(collectionViewLayout: UICollectionViewFlowLayout())
navigationController?.pushViewController(contProfile, animated: true)
}
// and I am making "self" this protocol in main controller's didload
let aCont = AController()
override func viewDidLoad() {
super.viewDidLoad()
AController.segueDelegate = self
}
// protocol
protocol segueFromController {
func segueFromController()
}
// this is presenting (A) controller code in main page
func openController() {
let preController = AController()
preController.modalPresentationStyle = .fullScreen
self.present(preController, animated: true, completion: nil)
}
First you need to make this segueDelegate weak
protocol segueFromController : class {
func segueFromController()
}
weak var segueDelegate: segueFromController?
func openController() {
let preController = AController()
preController.segueDelegate = self
preController.modalPresentationStyle = .fullScreen
self.present(preController, animated: true, completion: nil)
}
Try to dismiss without animation
self.dismiss(animated: false) {
self.segueDelegate?.segueFromController()
}

Return control to function when modal viewcontroller dismissed

I need to present a modal VC that sets a property in my presenting VC, and then I need to do something with that value back in the presenting VC. I have to be able to pass pointers to different properties to this function, so that it's reusable. I have the code below (KeyPickerTableViewController is the modal VC).
It should work, except not, because the line after present(picker... gets executed immediately after the picker is presented.
How do I get my presenting VC to "wait" until the modal VC is dismissed?
#objc func fromKeyTapped(_ button: UIBarButtonItem) {
print("from tapped")
setKey(for: &sourceKey, presentingFrom: button)
}
#objc func toKeyTapped(_ button: UIBarButtonItem) {
print("from tapped")
setKey(for: &destKey, presentingFrom: button)
}
fileprivate func setKey(for key: inout Key!, presentingFrom buttonItem: UIBarButtonItem) {
let picker = KeyPickerTableViewController()
picker.delegate = self
picker.modalPresentationStyle = .popover
picker.popoverPresentationController?.barButtonItem = buttonItem
present(picker, animated: true, completion: nil)
if let delKey = delegatedKey {
key = delKey
}
}
You could use delegate pattern or closure.
I would do the following
1. I would not use inout pattern, I would first call the popover and then separately update what is needed to be updated
2. In KeyPickerTableViewController define property var actionOnDismiss: (()->())? and setting this action to what we need after initialisation of KeyPickerTableViewController
I could show it in code, but the abstract you've shown is not clear enough to come up with specific amendments. Please refer the illustration below.
import UIKit
class FirstVC: UIViewController {
var key = 0
#IBAction func buttonPressed(_ sender: Any) {
let vc = SecondVC()
vc.action = {
print(self.key)
self.key += 1
print(self.key)
}
present(vc, animated: true, completion: nil)
}
}
class SecondVC: UIViewController {
var action: (()->())?
override func viewDidLoad() {
onDismiss()
}
func onDismiss() {
action?()
}
}
While presenting VC, add dismissing modal VC action in its completion handler, so that Viewcontroller will be presented after dismissal is completed
present(picker, animated: true, completion: { (action) in
//dismissal action
if let delKey = delegatedKey {
key = delKey
}
})

Removing Data on Maps with different view controllers

I am really struggling on an issue that I think is rather interesting and quite difficult. My application lets the user create annotation locations within a Mapview. They also have the option to edit and delete these locations in another modal view controller.
The issue I am facing is that when the user presses delete, which removes the location from firebase, the annotation is still displayed upon the map. I cannot reload my annotation data within the view did appear as this does not suit my application. I cant have my annotations being reloaded every time I bring up the Mapview.
I need to figure out a way to implement an annotation reload when the delete button is pressed. However, as this happens within my delete view controller (which does not contain the mapView) I cannot use the reload function. Is there a way to connect view controllers so that I can apply the reload function when delete is pressed?
Updated Code **
This is my map view controller:
class ViewController: UIViewController, SideBarDelegate, MGLMapViewDelegate, DeleteVCDelegate {
let EditSaveSpotController = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "EditVC") as! EditSaveSpotViewController
override func viewDidLoad() {
super.viewDidLoad()
EditSaveSpotController.delegate = self
}
func wholeRefresh() {
let uid = FIRAuth.auth()!.currentUser!.uid
let userLocationsRef = FIRDatabase.database().reference(withPath: "users/\(uid)/personalLocations")
userLocationsRef.observe(.value, with: { snapshot in
for item in snapshot.children {
guard let snapshot = item as? FIRDataSnapshot else { continue }
let newSkatepark = Skatepark(snapshot: snapshot)
self.skateparks.append(newSkatepark)
self.addAnnotation(park: newSkatepark)
}
})
if let annotations = mapView.annotations {
mapView.removeAnnotations(annotations)
}
for item in skateparks {
self.addAnnotation(park: item)
}
}
This is my delete view controller:
import UIKit
import Firebase
protocol DeleteVCDelegate {
func wholeRefresh()
}
class EditSaveSpotViewController: UIViewController {
var delegate: DeleteVCDelegate?
#IBAction func deleteSkateSpot(_ sender: Any) {
ref = FIRDatabase.database().reference(withPath: "users").child(Api.User.CURRENT_USER!.uid).child("personalLocations/\(parkId!)")
ref.observe(.value, with: { (snapshot) in
self.ref.setValue(nil)
self.dismiss(animated: true, completion: nil)
self.delegate?.wholeRefresh()
// self.delegate?.mainRefresh()
print("CheckWorking")
})
}
}
This is very high level and I did not have a chance to verify but it should be enough to get you going:
Modal Delete View
protocol DeleteVCDelegate {
func mainRefresh()
}
class DeleteVC: UIViewController {
var delegate: DeleteVCDelegate?
//your delete code
#IBAction func deleteSkateSpot(_ sender: Any) {
ref = FIRDatabase.database().reference(withPath: "users").child(Api.User.CURRENT_USER!.uid).child("personalLocations/\(parkId!)")
ref.observe(.value, with: { (snapshot) in
self.ref.setValue(nil)
//call to delegate
self.delegate?.mainRefresh()
})
}
}
MapView Class (implement DeleteVCDelegate)
class mapVC: MKMapViewDelegate, DeleteVCDelegate{
//when you present your DeleteVC set its delegate to the map view
let vc=(self.storyboard?.instantiateViewController(withIdentifier: "deleteVC"))! as! DeleteVC
//set the delegate
vc.delegate=self
//present deleteVC
self.present(vc, animated: true, completion:nil)
//implement delegate method of DeleteVC
func mainRefresh(){
//dismiss modal
self.dismiss(animated: true) {
//update view
self.loadLocations()
self.annotationRefresh()
}
}
}

Reload MainViewController when PopViewController dismissed

I have a main view controller and pop up controller.Please refer screen shots.
Code :
#IBAction func show(sender: AnyObject) {
var popView = popupviewcontroller(nibName:"popview",bundle:nil)
var popController = UIPopoverController(contentViewController: popView)
popController.popoverContentSize = CGSize(width: 450, height: 450)
popController.presentPopoverFromRect(sender.frame, inView: self.view, permittedArrowDirections: UIPopoverArrowDirection.Down, animated: true)
}
For the popupViewcontoller i used .xib.
When press save button data saved to core data.
Lets come to my problem, in my mainViewController i fetched data and fill them in dynamically created lables.That occurred when view load.I want to reload mainViewController when close button form popViewController pressed.
I tried within the close button my code are here, i just tried to reload the mainVc :
var mainVC = mainviewcontroller()
#IBAction func close(sender: AnyObject) {
self.dismissViewControllerAnimated(true, completion: nil)
//mainVc.viewDidLoad()
mainVC.reloadInputViews()
}
Does not give output.
Conclusion : I want a way to refresh view controller from another view in swift.
Thanks in advance.
Using UITableView only we can reload the data!,So we have to use table view custom cell textfield or Label. Now we can able to reload our custom textfield data.
Does your main ViewController use a UITableView?
You can use viewWillAppear and get the data again and use tableView.reloadData() to reload the data.
EDIT:
with var mainVC = mainviewcontroller() you're just making a new instance of your MainViewController. If you want to use reloadInputViews(), you can put it in viewWillLoad.
You should use protocol for it. You can read more about protocols here https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Protocols.html
You can make protocol in PopViewController
protocol PopViewControllerProtocol {
static var valuesChanged()
}
Later you should implement this protocol in MainViewController and refresh data.
Example:
File UIPopoverController.swift
protocol UIPopoverControllerDelegate{
func valuesChanged(changedValue:String)
}
class UIPopoverController: UIViewController {
var delegate: UIPopoverControllerDelegate! = nil
#IBAction func close(sender: AnyObject) {
self.dismissViewControllerAnimated(true, completion: nil)
//mainVc.viewDidLoad()
mainVC.valuesChanged("some value")
}}
File MainViewController.swift
class MainViewController: UIViewController, UIPopoverControllerDelegate
{
func valuesChanged(changedValue:String) {
//this will be called when popuviewcontroller call valueschanged on delegate object
}
#IBAction func show(sender: AnyObject) {
var popView = popupviewcontroller(nibName:"popview",bundle:nil)
var popController = UIPopoverController(contentViewController: popView)
popController.delegate = self;
popController.popoverContentSize = CGSize(width: 450, height: 450)
popController.presentPopoverFromRect(sender.frame, inView: self.view, permittedArrowDirections: UIPopoverArrowDirection.Down, animated: true)
}
}
With God Grace i found a solution that is , just create a Global variable
Var fetchedArray = Nsarray()
Then Write the following code in the popovoer controller save button
func save(){
// Write codes for saving data
let request = NSFetchRequest(entityName: "Enitity name")
request.returnsObjectsAsFaults = false
let Total = try? context.executeFetchRequest(request)
if let wrapResults = Total {
fetchedArray = wrapResults
}
}
Then fill the Labels by using fetchedArray.
Thanks
Swift 2, try
func adaptivePresentationStyleForPresentationController(controller: UIPresentationController) -> UIModalPresentationStyle
viewDidLoad()
return.None
Works for me