How to push controller after dismiss presented controller in swift? - 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()
}

Related

how can i dismiss a viewcontroller whilst at the same time segue onto a different one

I am currently unable to dismiss a viewcontroller and at the same time make the application move to a different controller without experiencing a few bugs. What my code does currently is when the timer 'secondsRemaining' hits zero it will dismiss the current viewcontroller it is on and will show the previous viewController that is displayed underneath the current viewcontroller being the 'homeScreenViewController'. Because of this it will not call the code- self.performSegue(withIdentifier: "gameScreenToHighscore", sender: self).
Current code:
#objc func updateTimer() {
if secondsRemaining > 0 {
secondsRemaining -= 1
timerLabel.text = "Timer:\(secondsRemaining)"
print(secondsRemaining)
} else {
self.dismiss(animated: true, completion: nil)
self.performSegue(withIdentifier: "gameScreenToHighscore", sender: self)
}
}
I think the best approach is that you handle the navigation in the parent ViewController, You can achieve this by making delegate.
In the ChildViewController:
#protocol ModalVCDelegate {
func navigateToTheSecondVC()
}
class ModalViewController {
weak delegate: ModalVCDelegate!
}
and you should call the delegate method when the timer has done.
delegate?.navigateToTheSecondVC()
And Also in the ParentViewController, you should set the delegate:
childVC.delegate = self
present(childVC, animated: true, completion: nil)
and then:
extension MainViewController: ModalVCDelegate {
func navigateToTheSecondVC {
dismiss(animated: true, completion: {
self.performSegue(withIdentifier: "gameScreenToHighscore", sender: self)
})
}
}

blank collection view in user login for the first time

Good day everyone,
I have root view controller set up as my HomeViewController, in this project i am using token based authentication ( which i am storing in user defaults ) and i am using token for all my API calls.
I have a check in my viewWillAppear method to check if there is access token present and then i make the api call in viewDidAppear to populate the collection view, and this works perfectly fine at all times except the first time.
If I log in for the first time it hits the viewWillAppear, viewDidAppear and then login screen pops up and once i authenticate the user and save it in the UserDefaults, dismiss the login screen and in the HomeViewController all i get is a spinner ( which means that viewDidAppear is also been called ) but if i close the app and open it again it all works fine.
What can i change in my code to make it work in the first time please and thank you!!
class HomeViewController: UIViewController {
// MARK: - Properties
let refreshControl = UIRefreshControl()
var publishedReportList: [ReportListDetail] = []
private let reportsCollectionView: UICollectionView = {
let viewLayout = UICollectionViewFlowLayout()
let collectionView = UICollectionView(frame: .zero, collectionViewLayout: viewLayout)
collectionView.register(ReportsCollectionViewCell.self, forCellWithReuseIdentifier: ReportsCollectionViewCell.identifier)
collectionView.backgroundColor = .systemBackground
return collectionView
}()
// MARK: - Initialisation
override func viewDidLoad() {
super.viewDidLoad()
reportsCollectionView.delegate = self
reportsCollectionView.dataSource = self
print(publishedReportList.count)
refreshControl.tintColor = .blue
refreshControl.addTarget(self, action: #selector(pullToRefresh), for: .valueChanged)
reportsCollectionView.addSubview(refreshControl)
reportsCollectionView.alwaysBounceVertical = true
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
// check auth status
handleNotAuthenticated()
}
override func viewDidAppear(_ animated: Bool) {
// call for reports
getReportUserLayout()
}
// MARK: - Handlers
#objc func pullToRefresh() {
// Code to refresh table view
getReportUserLayout()
}
fileprivate func getReportUserLayout() {
// publishedReportList.removeAll()
whySuchEmptyLabel.isHidden = true
spinner.show(in: view)
spinner.textLabel.text = "Loading Reports.."
DispatchQueue.main.async {
ReportsManager.shared.getReportData { [weak self] (listOfReports) in
guard let strongSelf = self else { return }
strongSelf.publishedReportList = listOfReports
if listOfReports.count == 0 {
strongSelf.whySuchEmptyLabel.isHidden = false
}
strongSelf.reportsCollectionView.reloadData()
strongSelf.spinner.dismiss()
strongSelf.refreshControl.endRefreshing()
}
}
}
private func handleNotAuthenticated() {
if UserDefaults.standard.string(forKey: "accessToken") == nil {
// show login view controller
let loginVC = LoginViewController()
loginVC.modalPresentationStyle = .overCurrentContext
present(loginVC, animated: false)
}
}
}
You are in the HomeViewController and presenting loginVC, so it will not trigger viewDidAppear or viewWillAppear because it is not disappeared from the app. You have to use closure or delegate or notification to communicate back to the HomeViewController. You can also use the Combine framework and save the state. Here is an example of using delegate.
// Add protocol
protocol ViewControllerDelegate {
func loggedIn()
}
class HomeViewController: UIViewController, ViewControllerDelegate {
private func handleNotAuthenticated() {
if UserDefaults.standard.string(forKey: "accessToken") == nil {
let loginVC = LoginViewController()
loginVC.modalPresentationStyle = .overCurrentContext
loginVC.viewDelegate = self
present(loginVC, animated: false)
}
}
}
class LoginViewController: UIViewController {
var viewDelegate: ViewControllerDelegate? = nil
func userLoggedIn() {
self.viewDelegate?.loggedIn()
}
}

How to Navigating from SwiftUI View to UIKit UIViewController

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)

self.dismiss resets parent view controller's screen data

I am presenting a view controller inside the ParentViewController:
let item = getCurrentItem()
let presentViewController = viewController as! TestViewController
presentViewController.id = item.id
presentViewController.dismissController { // my custom delegate to inform the parent vc
self.dismiss(animated: true, completion: nil) // presenting view controller is closing then viewWillAppear will be triggered
}
self.present(presentViewController, animated: true, completion: nil)
But when dismiss is called, the ParentViewController's global arrays and variables are back to their initial values.
class ParentViewController {
// MARK: - Stored Properties
private let refreshControl = UIRefreshControl()
private var items = [String:[Item]]()
private var screenLoaded: Bool = false
// MARK: - Controller Lifecycle
override func viewDidLoad() {
super.viewDidLoad()
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
// after dismiss is called viewWillAppear gets hit but the "screenLoaded" is always false
if !screenLoaded {
screenLoaded = true
}
// items is initialised as well
}
private func openPopover() {
let item = getCurrentItem()
let presentViewController = viewController as! TestViewController
presentViewController.id = item.id
presentViewController.dismissController { // my custom delegate to inform the parent vc
self.dismiss(animated: true, completion: nil) // presenting view controller is closing then viewWillAppear will be triggered
}
self.present(presentViewController, animated: true, completion: nil)
}
}
How can I keep the parent view controller's original state after the dismiss is called?
After hours of digging Apple's documentations finally I found the following solution and it works for me. Hope it helps someone in the future.
[https://developer.apple.com/documentation/uikit/uimodalpresentationstyle/overcurrentcontext][1]
presentViewController.modalPresentationStyle = .overCurrentContext
Also, if there is a tabbar, the bottom of the child view controller will be under it if you set the presentation style to overCurrentContext. The workaround is to present the child view controller from the window's rootViewController.
sender.view.window?.rootViewController?.present(viewController, animated: true, completion: nil)
Now when the dismiss is called viewWillAppear is not triggered and I can keep the parent view controller's data and it's state.

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
}
})