How to call IBAction of another UIViewController soon after presentViewController? - ios

So currently the user is in ViewController2, to transition to ViewController1 the presentViewController is being called.
Soon after ViewController1 opens up, there is an IBAction method that needs to be called.
How can this be accomplished? Any help would be greatly appreciated. Thank you!

The view controller can call it itself in either viewWillAppear or viewDidAppear.
self.myAction()
If the view controller is exclusively used in the context described you could do it unconditionally, otherwise if you need to do it conditionally - expose a Boolean:
public var doActionAfterAppear = false
public override viewDidAppear(animated isAnimated: Bool) {
super.viewDidAppear(animated: animated)
if self.doActionAfterAppear {
self.myAction()
self.doActionAfterAppear = false
}
}
And lastly before you present the second view controller or transition to it:
nextViewController. doActionAfterAppear = true

Related

How to prevent timer reset using pushViewController method?

I'm trying to keep a timer running even if I switch view controllers. I played around with the Singleton architecture, but I don't quite get it. Pushing a new view controller seems a little easier, but when I call the below method, the view controller that is pushed is blank (doesn't look like the view controller that I created in Storyboards). The timer view controller that I'm trying to push is also the second view controller, if that changes anything.
#objc func timerPressed() {
let timerVC = TimerViewController()
navigationController?.pushViewController(timerVC, animated: true)
}
You need to load it from storyboard
let vc = self.storyboard!.instantiateViewController(withIdentifier: "VCName") as! TimerViewController
self.navigationController?.pushViewController(timerVC, animated: true)
Not sure if your problem is that your controller is blank or that the timer resets. Anyway, in case that you want to keep the time in the memory and not deallocate upon navigating somewhere else I recommend you this.
Create some kind of Constants class which will have a shared param inside.
It could look like this:
class AppConstants {
static let shared = AppConstants()
var timer: Timer?
}
And do whatever you were doing with the timer here accessing it via the shared param.
AppConstants.shared.timer ...
There are different parts to your question. Sh_Khan told you what was wrong with the way you were loading your view controller (simply invoking a view controller’s init method does not load it’s view hierarchy. Typically you will define your view controller’s views in a storyboard, so you need to instantiate it from that storyboard.)
That doesn’t answer the question of how to manage a timer however. A singleton is a good way to go if you want your timer to be global instead of being tied to a particular view controller.
Post the code that you used to create your singleton and we can help you with that.
Edit: Updated to give the TimeManager a delegate:
The idea is pretty simple. Something like this:
protocol TimeManagerDelegate {
func timerDidFire()
}
class TimerManager {
static let sharedTimerManager = TimerManager()
weak var delegate: TimeManagerDelegate?
//methods/vars to manage a shared timer.
func handleTimer(timer: Timer) {
//Put your housekeeping code to manage the timer here
//Now tell our delegate (if any) that the timer has updated.
//Note the "optional chaining" syntax with the `?`. That means that
//If `delegate` == nil, it doesn't do anything.
delegate?.timerDidFire() //Send a message to the delegate, if there is one.
}
}
And then in your view controller:
//Declare that the view controller conforms to the TimeManagerDelegate protocol
class SomeViewController: UIViewController, TimeManagerDelegate {
//This is the function that gets called on the current delegate
func timerDidFire() {
//Update my clock label (or whatever I need to do in response to a timer update.)
}
override func viewWillAppear() {
super.viewWillAppear()
//Since this view controller is appearing, make it the TimeManager's delegate.
sharedTimerManager.delegate = self
}

Disable the interactive dismissal of presented view controller

iOS 13 introduces a new design of modalPresentationStyle .pageSheet (and its sibling .formSheet) for modally presented view controllers…
…and we can dismiss these sheets by sliding the presented view controller down (interactive dismissal). Although the new "pull-to-dismiss" feature is pretty useful, it may not always be desirable.
THE QUESTION: How can we turn the interactive dismissal off?
- Bear in mind we keep the presentation style the same.
Option 1:
viewController.isModalInPresentation = true
(Disabled interactive .pageSheet dismissal acts like this.)
Since the iOS 13, UIViewController contains a new property called isModalInPresentation which must be set to true to prevent the interactive dismissal.
It basically ignores events outside the view controller's bounds. Bear that in mind if you are using not only the automatic style but also presentation styles like .popover etc.
This property is false by default.
From the official docs: If true, UIKit ignores events outside the view controller's bounds and prevents the interactive dismissal of the view controller while it is onscreen.
Option 2:
func presentationControllerShouldDismiss(_ presentationController: UIPresentationController) -> Bool {
return false
}
Since the iOS 13, UIAdaptivePresentationControllerDelegate contains a new method called presentationControllerShouldDismiss.
This method is called only if the presented view controller is not dismissed programmatically and its isModalInPresentation property is set to false.
Tip: Don't forget to assign presentationController's delegate. But be aware, it is known that even just accessing the presentationController can cause a memory leak.
If you want the same behaviour as it's in previous iOS version (< iOS13) like model presentation in fullscreen, just set the presentation style of your destination view controller to UIModalPresentationStyle.fullScreen
let someViewController = \*VIEW CONTROLLER*\
someViewController.modalPresentationStyle = .fullScreen
And if you are using storyboard just select the segua and select Full Screen form the Presentation dropdown.
If you just want to disable the interactive dismissal and keep the new presentation style set UIViewController property isModalInPresentation to true.
if #available(iOS 13.0, *) {
someViewController.isModalInPresentation = true // available in IOS13
}
The property isModalInPresentation might help.
From the documentation:
When you set it to true, UIKit ignores events outside the view controller's bounds and prevents the interactive dismissal of the view controller while it is onscreen.
You can use it like this:
let controller = MyViewController()
controller.isModalInPresentation = true
self.present(controller, animated: true, completion: nil)
If you have some business logic, something like all fields should be filled before dismissing, you should:
On ViewDidLoad if your ViewController has been presented within a Navigation Controller:
func viewDidLoad() {
self.navigationController?.presentationController?.delegate = self
}
If not, simply use
func viewDidLoad() {
self.presentationController?.delegate = self
}
Then implement the delegate method:
extension ViewController: UIAdaptivePresentationControllerDelegate {
func presentationControllerShouldDismiss(_ presentationController: UIPresentationController) -> Bool {
guard let text = firstName.text, text.isEmpty else { return false }
guard let text = lastName.text, text.isEmpty else { return false }
...
return true
}
}
If you are using storyboards to layout your UI I have found the best way to disable this interactive dismissal when using a navigation controller is to change the presentation of the Navigation Controller in the attribute inspector from Automatic to Full Screen. All view controllers in your navigation stack will then be full screen and will not be able to be dismissed by the user.
Attribute Inspector showing presentation option for the navigation controller
Apple shared a sample code about it at this link
It uses isModalInPresentation as many users suggestion.
All solutions are good, but in my case, I need an option to stop movement.
So this is a code for that.
if you want to block movement:
self.yourViewController?.presentedView?.gestureRecognizers?[0].isEnabled = false
And if you want to unblock movement:
self.yourViewController?.presentedView?.gestureRecognizers?[0].isEnabled = true

UIStoryboardSegue animates property in subclass

I have a UIStoryboardSegue subclass for replacing current view controller with next view controller.
As we have a Animates property in interface editor, I want to access this property in the subclass.
My code is following:
class ReplaceSegue: UIStoryboardSegue {
override func perform() {
var viewControllers = source.navigationController?.viewControllers.dropLast() ?? []
viewControllers.append(destination)
source.navigationController?.setViewControllers(viewControllers.map {$0}, animated: true) // I dont want this `true` to be hardcoded
}
}
As per comments in UIStoryBoardSegue class
The segue runtime will call +[UIView setAnimationsAreEnabled:] prior
to invoking this method, based on the value of the Animates checkbox
in the Properties Inspector for the segue.
So obviously you can read the value of animate check box by using
UIView.areAnimationsEnabled
So in my custom segue
class MySegue: UIStoryboardSegue {
override func perform() {
debugPrint(UIView.areAnimationsEnabled)
}
}
This prints false if animate checkbox is unchecked or true if it is checked :)
So in your case
class ReplaceSegue: UIStoryboardSegue {
override func perform() {
var viewControllers = source.navigationController?.viewControllers.dropLast() ?? []
viewControllers.append(destination)
source.navigationController?.setViewControllers(viewControllers.map {$0}, animated: UIView.areAnimationsEnabled)
}
}
I hope whats happening is already clear, incase you still have doubt, here is the explanation, iOS checks the animates checkbox value and uses it to set whether animations are enabled or not by calling setAnimationsAreEnabled with the value of animates check box in interface prior to calling perform() method.
So when the control reaches inside perform you can be assured that iOS has already read the value of animates check box and used it to set setAnimationsAreEnabled all you have to do now is to ask areAnimationsEnabled to get the value of animates check box.
So that should provide you the value of animates checkbox :)
Hope it helps :)
You shouldn't need a UIStoryboardSegue subclass for this. The docs state "You can subclass UIStoryboardSegue in situations where you want to provide a custom transition between view controllers". This means that a replacement without without any animation isn't a custom transition, thus shouldn't use a segue subclass.
The correct way to do replacement is to use a Show Detail (e.g. Replace) segue and inside the parent view controller that is managing the child view controllers implement the method showDetailViewController and replace the children, e.g.
#implementation DetailNavigationController
- (void)showDetailViewController:(UIViewController *)vc sender:(id)sender{
[self setViewControllers:#[vc] animated:NO];
}
If you didn't know, the Show Detail segue (after magically instantiating the destination view controller) has a perform method that just calls showDetailViewController on self, and the base UIViewController implementation searches up the view controller hierarchy looking for one that overrides showDetailViewController, so you can intercept it and perform your custom code, before say it goes up to another parent that might implement it also like a split view.

boolean variable automatically initializes when changing viewcontrollers in iOS

In My application contain five different tabs. When I switch between tabs initialization of boolean variable automatically called again and again.
When I put a breakpoint on declaration/initialization variable it will be automatically called. This will leads to change my boolean variable value to true. Please go through the following code
How to resolve this issue. Please help me
class HomeViewController: BaseViewController {
var isEnabled: Bool = true
}
override func viewWillAppear(_ animated: Bool) {
isEnabled = true
}
override func viewWillDisappear(_ animated: Bool) {
isEnabled = false
}
You are using tab view for your app. When you switch from one tab to other tab viewWillAppear called and your boolean variable isEnabled is set to true and when move to new tab it called viewWillDisappear and set to false. If you don't want to change in isEnabled variable then remove from viewWillAppear and viewWillDisappear then value will not be changed. You will get last value assigned to variable.
viewDidLoad method will call only once a life time of viewController and that is when viewController object will first load in memory. where as viewWillAppear method will call every time when a view will appear to screen or you can say will be topViewController...
Explanation: Tab1 associated with viewController1 and tab2 is associated with viewController2. Now when you will run your app and you will see tab one is selected and viewController1 is on view and you want to change to tab2, when you will tap on tab2 then tabVieController2's object will create and load to memory first time hence its viewDidLoad method will call, and soon after that it will appear to window and viewWillAppear will also get call. Now if you you try changing tabs by click on them only viewWillAppear methods will get called for both, as they are in memory already.

Swift: Perform a function on ViewController after dismissing modal

A user is in a view controller which calls a modal. When self.dismissViewController is called on the modal, a function needs to be run on the initial view controller. This function also requires a variable passed from the modal.
This modal can be displayed from a number of view controllers, so the function cannot be directly called in a viewDidDisappear on the modal view.
How can this be accomplished in swift?
How about delegate?
Or you can make a ViewController like this:
typealias Action = (x: AnyObject) -> () // replace AnyObject to what you need
class ViewController: UIViewController {
func modalAction() -> Action {
return { [unowned self] x in
// the x is what you want to passed by the modal viewcontroller
// now you got it
}
}
}
And in modal:
class ModalViewController: UIViewController {
var callbackAction: Action?
override func viewDidDisappear(_ animated: Bool) {
let x = … // the x is what you pass to ViewController
callbackAction?(x)
}
}
Of course, when you show ModalViewController need to set callbackAction like this modal.callbackAction = modalAction() in ViewController
The answer supplied and chosen by the question asker (Michael Voccola) didn't work for me, so I wanted to supply another answer option. His answer didn't work for me because viewDidAppear does not appear to run when I dismiss the modal view.
I have a table and a modal VC that appears and takes some table input. I had no trouble sending the initial VC the modal's new variable info. However, I was having trouble getting the table to automatically run a tableView.reloadData function upon dismissing the modal view.
The answer that worked for me was in the comments above:
You likely want to do this using an unwind segue on the modal, that
way you can set up a function on the parent that gets called when it
unwinds. stackoverflow.com/questions/12561735/… – porglezomp Dec 15
'14 at 3:41
And if you're only unwinding one step (VC2 to VC1), you only need a snippet of the given answer:
Step 1: Insert method in VC1 code
When you perform an unwind segue, you need to specify an action, which
is an action method of the view controller you want to unwind to:
#IBAction func unwindToThisViewController(segue: UIStoryboardSegue) {
//Insert function to be run upon dismiss of VC2
}
Step 2: In storyboard, in the presented VC2, drag from the button to the exit icon and select "unwindToThisViewController"
After the action method has been added, you can define the unwind
segue in the storyboard by control-dragging to the Exit icon.
And that's it. Those two steps worked for me. Now when my modal view is dismissed, my table updates. Just figured I'd add this, in case anyone else's issue wasn't solved by the chosen answer.
I was able to achieve the desired result by setting a Global Variable as a boolean value from the modal view controller. The variable is initiated and made available from a struct in a separate class.
When the modal is dismissed, the viewDidAppear method on the initial view controller responds accordingly to the value of the global variable and, if needed, flips the value on the global variable.
I am not sure if this is the most efficient way from a performance perspective, but it works perfectly in my scenario.

Resources