Need help stopping processes when I change view controllers in swift. - ios

I am working on an app with multiple view controllers. I have a main menu view controller, game view controllers with a timer and one game over view controller.
Main menu has a button that takes you to game view controller. There is a timer that starts and when it reaches zero it automatically takes you to game over view controller.
The problem is that if I am in the game view controller and decide to go back to main menu the timer continues.
I then get a warning message:
Warning: Attempt to present <...> on <...> whose view is not in the window hierarchy!
How do I get the timer to stop when I change view controller?

invalidate the timer before the view disappears:
override func viewWillDisappear(animated: Bool) {
timer.invalidate()
timer = nil
super.viewWillDisappear(animated)
}

Related

How to hide child view from parent view after some delay

hide child view after few seconds
I set time for that but i cant access child viewcontroller in my timer function
I tried dissmiss , removefromparent about not worked.
only self.view.isHidden = true is worked
I can't place it in timer
My Parent view
Child View:
Button code:
Timer code:
In Like_btn_Action() function, you:
create an instance of LikeViewController
add it as a child view controller
add its view to your view
set that view's background color
and then the function exits. At this point, you no longer have a reference to your instance of LikeViewController ... likeVC has gone out of scope.
You need to use a class-level var to maintain the reference to the loaded child view controller, along these lines:
var likeVC: LikeViewController?
#IBAction func Like_btn_Action(_ sender: Any) {
likeVC = self.storyboard?.instantiateViewController( etc ...)
}
Then, when you want to remove the view you added, you can "get to it" via:
likeVC.view.removeFromSuperview()
for example.

Swift 3 - Dismissing view controller after async job

I am using Swift 3, Xcode 8.2.
I have a view controller on which there is a button. When the button is pressed, it runs the buttonPressed function below. It kicks off an asynchronous job which takes a few seconds to complete during which a spinning gear activity indicator pops up. Once the job is finished, I want the activity indicator to dismiss and the view controller to dismiss as well to the VC that it originally came from.
func buttonPressed() {
// Set up some stuff here
...
// this code block here is to asynchronously run the processing job
// while the activity indicator gear spins
self.displaySpinningGear()
DispatchQueue.main.async {
self.doSomeJobProcessing()
}
self.dismiss(animated: true, completion: nil)
// also dismiss the camera view
self.presentingViewController?.dismiss(animated: true, completion: nil) // error here
}
However, I get an error on that last statement:
[Assert] Trying to dismiss the presentation controller while
transitioning already.
(<_UIAlertControllerAlertPresentationController: 0x109e3b190>)
2017-03-03 21:37:56.833899 EOB-Reader[27710:6869686] [Assert]
transitionViewForCurrentTransition is not set, presentation controller
was dismissed during the presentation?
(<_UIAlertControllerAlertPresentationController: 0x109e3b190>)
Any help would be greatly appreciated.
Perform anyone transition of these at a time.
if let viewController = presentingViewController {
// This block will dismiss both current and a view controller presenting current.
viewController.dismiss(animated: true, completion: nil)
} else {
// This block will dismiss only current view controller
self.dismiss(animated: true, completion: nil)
}
Two transition operations cannot be performed simultaneously.
In your source code you are dismissing current view controller with animation. Now animation generally takes time around 0.25 second to complete its operation.
Now, in next line you are trying to dismiss a ( ** ) view controller that has presented current view controller. (In your case ( ** ) view controller is also presented by some other view controller.) so, within a fraction of seconds/milliseconds ( ** ) view controller will also try to dismiss it self with animation.
At this time your current view controller is being dismissed and (**) view controller will also start dismiss operation. So, both operations conflict each other on main executing thread. And results into an issue, you are facing.
Also, share your code for block
self.doSomeJobProcessing()
Share here, if you've set any other view/controller transition operations here.
Remember that when you dispatch asynchronous you're typically dispatching to background thread to prevent UI blocking. All on screen (UI) components need be modified on the main thread. So start your progress spinner from the main thread, dispatch your heavy lifting to the background, then redispatch to the main thread that you'd like to. this.dismissviewcontroller:animated
Will update with code shortly.

Swift: When Does the Completion Block of Dismiss View Get Executed?

When does the completion block of dismiss view get executed?
Is it after or before the user sees the view dismissed?
I have this code to make a toast with a message inside completion block but never see the toast after this view dismissed.
self.dismiss(animated: true, completion: {
self.view.makeToast(message: "Employee has been assigned successfully.", duration: 2.0, position: HRToastPositionCenter as AnyObject, title: "Succeeded!")
})
What I want is the user can see the toast when the view gets completely dismissed?
How do I do this ?
You can delegate the event from presented controller to the parent and handle it there.
In EmployeePickerViewController (or whatever your modal controller is called):
#protocol EmployeePickerDelegate {
func employeeAssigned()
}
class EmployeePickerViewController {
weak delegate: EmployeePickerDelegate!
}
When employee assignment is finished just call delegate's method:
delegate?.employeeAssigned()
In MainViewController when you present modally:
employeePicker.delegate = self
present(employeePicker, animated: true, completion: nil)
In MainViewController below:
extension MainViewController: EmployeePickerDelegate {
func employeeAssigned {
dismiss(animated: true, completion: {
self.view.makeToast(message: "Employee has been assigned successfully.", duration: 2.0, position: HRToastPositionCenter as AnyObject, title: "Succeeded!")
})
}
}
For UIViewController.dismiss(animated:completion:)
The completion handler is called after the viewDidDisappear(_:) method
is called on the presented view controller.
Source
For UIViewController.present(_:animated:completion:)
The completion handler is called after the viewDidAppear(_:) method is
called on the presented view controller.
Source.
If you don't know when that is, this is the order of the UIViewController load, appear and dissapear methods
viewDidLoad
normally we initialize data objects and controls. It will create all
the necessary memory for all controls/data objects for this view. i.e.
In the above case, anotherView and btnView, they will keep the same
memory addresses for the whole life cycle.
viewWillAppear
Called before the view is added to the windows’ view hierarchy. So it
is ideal for updating the viewcontroller’s data.
viewDidAppear
Called after the view is added to the windows’ view hierarchy.
viewWillDisappear
Called before the view is removed from the windows’ view hierarchy.
viewDidDisappear
Called after the view is removed from the windows’ view hierarchy.
Source
You are calling a dismiss on self, so every reference of that will be dealloc. your self.view doesn't exist anymore I guess.
The completion block is executed after the View Controller has been dismissed. This means your view is no longer on screen. I think you want to render the toast inside that view, which is not possible because it is off screen.
The toast will be shown inside your dismissed view. Since the view has disappeared you won't see its toast. You might want to show the toast in the following screen that appears after the dismissed view.
Maybe could be easier to show the toast on the window instead of a particular view?
UIApplication.shared.keyWindow?.makeToast(message: "Employee has been assigned successfully.", duration: 2.0, position: HRToastPositionCenter as AnyObject, title: "Succeeded!")

(Swift) Putting a NSNotificationCenter observer in ViewDidLoad isn't working for my project... Where should I put it?

I'm using SWRevealController to display 3 (left, center, right) panels. Basically, the right panel is a tableview of numbers and the middle panel shows possible even divisions when a user clicks on a number. I connected a segue to the IBAction of a reusable tablecell in the right view controller which loads the MainViewController. This all works fine. The problem is that if the number can't be divided evenly it triggers a notification, which the main view controller observes on ViewDidLoad. This notification sets the alpha of a pseudo-"alert" (UIView at the bottom of screen) to 1.0 for 4 seconds, at which point it returns to 0. Unfortunately this is where the problem starts: the notification box appears for a brief second while the animation runs but when the main viewcontroller finishes animating, the alert box disappears. I have a hunch it's because the ViewDidLoad fires at this point and resets the NSNotificationOberser – if I remove the segue on Touch Up Inside and manually switch view controllers the alert-box remains present.
Can you help me think of what I'm getting wrong? Like I said, I think it's because the observer is initialized in the ViewDidLoad. Assuming this is the case, where should I initialize the observer so that this doesn't happen anymore?
Basically the main VC displays a calculator, the code for which runs in a Calculator.swift file. If, when the number is passed through Calculate(), there is an error, it triggers an "alert" which the main VC picks up on, then reveals the box so that the user knows. Each time Calculate() is called, it logs the user's calculation to a tableview in the RVC – idea being they can reload previous calculations. Is this an improper usage of Notification Center?
The way I want the timeline of events to be:
User clicks on cell in the right panel
Main view controller (the calculator) is pushed via segue
Calculate() is called on the selected number, if there is a remainder a notification is triggered
The main view displays the results from Calculate(), if an alert fired then it would unhide a popup box on main view.
What is currently happening:
User clicks on the cell in right panel
Calculate() is called on the number, if there is a remainder the notification fires
Before the main view is pushed via segue, I can see briefly in the animation the result of the trigger firing and the calculation
As soon as the segue animation completes the view hides
My main VC Code (PopUpView is the alert box)
class CalculatorVC: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
NSNotificationCenter.defaultCenter().addObserver(self, selector: "triggerAlert:", name: "alert", object: nil)
self.PopUpView.alpha = 0.0
}
the triggerAlert function:
func triggerAlert(notification: NSNotification) {
PopUpView.alpha = 1.0
let returnedRemainder = (notification.userInfo)
let sample: Double = (returnedRemainder!["userTotal"]as! Double)
self.label.text = "Warning! Remainder: \(Double(round(100 * sample)/100))"
}
the tableView didSelect of the right VC:
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
newUser.calculate()
//code here assigns the value in the selected table cell to newUser
}
Then in the Calculate() function:
dispatch_async(dispatch_get_main_queue(), {
NSNotificationCenter.defaultCenter().postNotificationName("alert", object: nil, userInfo: userTotalDictionary)
}
Notification observation/de-observation should generally be balanced between viewDidAppear/viewWillDisappear or viewWillAppear/viewDidDisappear. Either pair is usually fine, but it's wise to keep things that happen "when just offscreen" separate from things that happen "when just onscreen".
viewDidLoad is a poor place to set up observation, because you don't have a good place to balance removing the observation (viewDidUnload no longer exists). You should only remove observations in deinit that you set up in init, and view controllers really should never be observing things when they are not on screen.

Right way or event to choose what view load in swift

I'm working in an app that logs in an user if there isn't another user already logged in at launch time. This way the first view to appear should be the Login View. But in the case there is a logged user already, the first view appearing should be the main menu. Im handling this with the viewWillAppear function and it's working, but I don't know if this is the correct approach or how it should be handle in this situations.
Here is my code. My first view is MainMenuVC in which I control if there is a logged user or not, then I choose if stay in main menu view or push my login view.
class MainMenuVC: UIViewController {
override func viewWillAppear(animated: Bool) {
if (UserMgr.users.count == 0){
var vc1:LoginVC = self.storyboard?.instantiateViewControllerWithIdentifier("LoginView") as LoginVC
self.navigationController?.pushViewController(vc1, animated: false)
}
else
{
//I do nothing so this view is loaded
}
}
I don't know if i should use another ViewController and implement the function loadView() to decide what view load, but the problem is make that view work with the story board and my navigation controller.
Any suggestions?
Basically you will have two different view controllers, one for the login screen (VCLogin) and one for the main menu (VCMainMenu). Now, in your AppDelegate there are methods which are called, when the app launches respectively when it appears. So, place the code checking whether a user is logged in there and make the appropriate view controller the root view controller, e.g.
let navigationController = window.rootViewController as UINavigationController
navigationController.rootViewController =
userIsLoggedIn ? mainMenuViewController : loginViewController

Resources