I have a collection view of cells that scroll horizontally. Every time I log out, my main view controller gets dismissed to the log in screen. But when I log back in, it seems that my main view controller was never removed from memory. I say this because my collection view cells are in the same position as they were before I logged out.
I tried everything, from viewWillAppear and reloading the data, to trying to bring my log in view controller as my root window. Any suggestions?
(To be clearly demonstrate this, when you log out of Instagram (from the settings view controller), a log-in view is presented modally. When you log back in, you are presented with the Home feed, instead of the settings view controller. I want to reset the hierarchy of data)
class MainController: UICollectionViewController, UICollectionViewDelegateFlowLayout {
let feedId = "feedId"
var allUserCategory: AllUserCategory?
override func viewDidLoad() {
super.viewDidLoad()
collectionView?.pagingEnabled = true
collectionView?.backgroundColor = UIColor(r: 250, g: 250, b: 250)
collectionView?.registerClass(AllUserCategory.self, forCellWithReuseIdentifier: feedId)
navigationItem.rightBarButtonItem = UIBarButtonItem(title: "Log Out", style: .Plain, target: self, action: #selector(handleLogout))
navigationItem.rightBarButtonItem?.tintColor = UIColor.blueColor()
navigationController?.navigationBar.translucent = false
checkIfUserLoggedIn()
}
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
self.collectionView?.reloadData()
}
func handleLogout() {
print("Logged out")
do {
try FIRAuth.auth()?.signOut()
} catch let logoutError {
print(logoutError)
}
allUserCategory?.users.removeAll()
let loginController = LoginController()
let loginNav = UINavigationController(rootViewController: loginController)
presentViewController(loginNav, animated: true, completion: nil)
}
}
Add NSLog in dealloc
And maybe you have some memory leak in your code(MainViewController).
Use weak reference for IBOutlet and custom delegate
Related
I have this kind of flow page :
Setting Page --> Enter Password --> Go to Page X
So in setting page, in order to go to page X, I have to enter a password using pop up view.
When I'm in page X and i want to go back to previous page, I go to enter password pop up view, instead of the setting page.
How do I skip that page?
My pop up view code is something like this :
let btn = storyboard?.instantiateViewController(withIdentifier: "PopUpView") as! PopUpView
addChild(btn)
btn.view.frame = view.frame
view.addSubview(btn.view)
btn.didMove(toParent: self)
I'm fairly new here, any help will be appreciated.
Thankyou.
use the settings view controller's present function to open the popup.
//In your settings view
func openPopup() {
let btn = storyboard?.instantiateViewController(withIdentifier: "PopUpView") as! PopUpView
self.present(btn, animated: true, completion: nil)
}
When the user clicks ok, call dismiss on your popup view and use the closure to initiate the page opening to X
//In your 'PopUpView' On the OK button action of your popup
func didTouchOk() {
self.dismiss(animated: true) { [weak self] in
guard let self = self else { return }
//Put your open page X code here
let XView = storyboard?.instantiateViewController(withIdentifier: "XViewController") as! XView
self.present(XView, animated: true, completion: nil)
}
}
if you are using navigationController:
Declare a protocol in your PopUpView
protocol popupDelegate: class {
func userDidTouchOkInPopup()
}
create a weak var in your PopUpView
weak var delegate: popupDelegate? = nil
In your settings viewcontroller:
Assign delegate to popup before pushing
func openPopup() {
let btn = storyboard?.instantiateViewController(withIdentifier:
"PopUpView") as! PopUpView
btn.delegate = self
self.navigationController?.pushViewController(btn, animated: true)
}
Implement an extension for this protocol
extension SettingsViewController: popupDelegate {
func userDidTouchOkInPopup() {
self.navigationController?.popViewController(animated: true)
let XView = storyboard?.instantiateViewController(withIdentifier:
"XViewController") as! XView
self.navigationController?.pushViewController(XView, animated: true)
}
}
Modify the Ok action in your PopUpView
//In your 'PopUpView' On the OK button action of your popup
func didTouchOk() {
self.delegate?.userDidTouchOkInPopup()
}
If you are in navigationController hierarchy, use navigationController.popToViewController to go to the specific ViewController.
In ViewDidLoad, hide the present backButton and create a new One and associate action with it.
override func viewDidLoad() {
super.viewDidLoad()
self.navigationItem.hidesBackButton = true
let newBackButton = UIBarButtonItem(title: "Back", style: .plain, target: self, action: #selector(backButtonFunction))
self.navigationItem.leftBarButtonItem = newBackButton
}
Inside backButtonFunction, you can put the code
#objc func backButtonFunction(){
if let navController = self.navigationController {
for controller in navController.viewControllers {
if controller is SettingsViewController { // Change name of ViewController accordingly
navController.popToViewController(controller, animated:true)
break
}
}
}
}
Greetings StackOverflow!
I'm really new to Swift development, so don't go all crazy on me please :3
I've created a login page for my mobile app (Firebase Email + Password)
I've implemented an "auto login" function - so when a user logs in for the first time, the user stays logged after closing the app & opening it.
The problem is that every time a user opens the app, there is a slight delay between viewDidLoad and viewDidAppear -> resulting in that every time the app opens, you can see the login screen for about 0.4 sec until it automatically sings the user in.
Once the user logs in the user are segued (no animation) to my UITabBarController.
I've provided the code for my LoginViewController.
LoginViewController is my first Controller the app loads in.
Best regards
class LoginViewController: UIViewController {
#IBOutlet weak var loginEmail: UITextField!
#IBOutlet weak var loginPassword: UITextField!
#IBOutlet weak var loginBorder: UIButton!
let userDefault = UserDefaults.standard
override func viewDidLoad() {
super.viewDidLoad()
}
override func viewDidAppear(_ animated: Bool) {
if userDefault.bool(forKey: "usersignedin") {
performSegue(withIdentifier: "login", sender: self)
}
//Border color button//
loginBorder.layer.borderColor = UIColor.white.cgColor
//Border color button//
//Hide Keyboard Use
self.hideKeyboardWhenTappedAround()
//Hide Keyboard Use
//start padding function for login
addPaddingAndBorder(to: loginEmail)
addPaddingAndBorder(to: loginPassword)
//start padding function for login
}
#IBAction func loginButton(_ sender: UIButton) {
Auth.auth().signIn(withEmail: loginEmail.text!, password: loginPassword.text!) { (user, error) in
if user != nil {
self.userDefault.set(true, forKey: "usersignedin")
self.userDefault.synchronize()
self.performSegue(withIdentifier: "login", sender: self)
} else {
let alert = UIAlertController(title: "Invalid Email or Password", message: nil, preferredStyle: .alert)
let okButton = UIAlertAction(title: "Ok", style: .default, handler: nil)
alert.addAction(okButton)
self.present(alert, animated: true, completion: nil)
}
}
}
}
We're doing something similar to this in my current project, but we decide which controller to show first in the AppDelegate.
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
var storyboard: UIStoryboard!
if isLoggedIn {
storyboard = UIStoryboard(name: "main", bundle: nil)
}
else {
storyboard = UIStoryboard(name: "login", bundle: nil)
}
let viewController = storyboard.instantiateInitialViewController()!
viewController.loadViewIfNeeded()
window.rootViewController = viewController
}
This has the advantage that when the app loads it immediately take you to the correct ViewController without showing the wrong one briefly first.
This is correct system behavior. ViewDidLoad will always be called much sooner than ViewDidAppear. IF you want to change the base view controller based on userDefault.bool(forKey: "usersignedin"), add that logic to where you set the main view controller and swap them if already signed in.
Change the order of view controllers. Always load your UITabBarController first (as initial controller). Then in your UITabBarController viewDidLoad, check if the user is logged-in or not. If not, then show the login screen. This way your UITabBarController is always loaded first and then based on auto-logn condition, your login screen is displayed. So when the user is auto looged-in then your delay issue will not come.
I have used this concept in all of my apps and it works fine.
Move the call to performSegue(withIdentifier:sender:) to viewWillAppear(_:) instead of viewDidAppear(_:) in case usersignedin = true.
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
if userDefault.bool(forKey: "usersignedin") {
performSegue(withIdentifier: "login", sender: self)
}
}
Also, the response of signIn method is not received on the main thread. Since you're not updating the UI on the main thread, that's why it is resulting in the delay.
Embed the UI part in closure of signIn method in DispatchQueue.main.async
Auth.auth().signIn(withEmail: loginEmail.text!, password: loginPassword.text!) { (user, error) in
if user != nil {
self.userDefault.set(true, forKey: "usersignedin")
self.userDefault.synchronize()
DispatchQueue.main.async { //here..........
self.performSegue(withIdentifier: "login", sender: self)
}
}
else {
let alert = UIAlertController(title: "Invalid Email or Password", message: nil, preferredStyle: .alert)
let okButton = UIAlertAction(title: "Ok", style: .default, handler: nil)
alert.addAction(okButton)
DispatchQueue.main.async { //here..........
self.present(alert, animated: true, completion: nil)
}
}
}
viewDidAppear(_:) happens right after the view appears. I suggest moving your code from viewDidAppear(_:) to viewWillAppear(_:).
you can also add your login code in the viewDidLoad() because this method is called even before viewWillAppear(_:).
in your case your code will be:
override func viewDidLoad() {
super.viewDidLoad()
if userDefault.bool(forKey: "usersignedin") {
performSegue(withIdentifier: "login", sender: self)
}
}
More info:
when a view is loading, there are different methods which get called in a specific order:
viewDidLoad() (once and only when the object is being created.)
viewWillAppear(_:)
[View appears on the screen]
viewDidAppear(_:)
when you push another view controller over this one, this view controller won't be removed from the view controllers' stack. the important thing is when you come back to this view controller, this time viewDidLoad() won't get called. so only these methods get called again in this order:
viewWillAppear(_:)
[View appears on the screen]
viewDidAppear(_:)
Best way is to decide in AppDelegate which controller to load based on Login Status
let navigationController = UINavigationController()
navigationController.navigationBar.barStyle = .black
let storyboard = UIStoryboard(name: "Main", bundle: nil)
if UserDefaults.standard.object(forKey: "Token") != nil{
let initialViewController = storyboard.instantiateViewController(withIdentifier: "HomeViewController")
navigationController.viewControllers = [initialViewController]
} else{
let initialViewController = storyboard.instantiateViewController(withIdentifier: "LoginViewController")
navigationController.viewControllers = [initialViewController]
}
navigationController.navigationBar.barTintColor = UIColor.black
navigationController.navigationBar.tintColor = UIColor.white
navigationController.navigationBar.titleTextAttributes = [NSAttributedString.Key.foregroundColor : UIColor.white]
self.window?.rootViewController = navigationController
self.window?.makeKeyAndVisible()
I have a tab bar view. In that, one of three tabs is a contacts app. In that contacts app, there are three pages, one to display all the contacts, second to display one particular contact selected in the first VC, and third is to add a new contact. (Pretty much similar to the built-in iOS app) How can I create a fluent flow between these three pages. (Do not forget they should be inside one tabbar)
I've added all the required buttons.
First Page - Display all contacts
Second Page - Show one particular contact
Third Page - Add new contact
I've embedded 3 navigation view controller (one to each of the 3 pages)
First page has 'add' button that should lead to third page.
Second page has two buttons, one to go back to first and the other to go to third.
Third has two buttons, one each to go the each of the other two pages.
I've created these buttons programmatically inside the navigation bar using this function -
override func viewDidAppear(_ animated: Bool) {
navigationItem.title = "Add New Contact"
navigationItem.rightBarButtonItem = UIBarButtonItem(title: "Done", style: UIBarButtonItem.Style.plain, target: self, action: #selector(theTransition))
}
#objc func theTransition() {
let second = self.storyboard?.instantiateViewController(withIdentifier: "OneNavID") as! OneNavigationViewController
self.present(second, animated: true, completion: nil)
}
(The above code is an example of going from page 3 to 1)
These buttons are working properly, but they show a new page which is out of the tab bar controller.
I want them to stay inside the tab view. Kindly help! Do ask if you have any doubt in the question. I've tried best to explain it in simple and short words.
I've looked on the internet a lot, but I couldn't find any instance where each of three view controllers have one navigation controller each (which I assume is needed since each of them has incoming and outgoing links to/from other pages).
Second Page -
override func viewDidAppear(_ animated: Bool) {
navigationItem.rightBarButtonItem = UIBarButtonItem(title: "Edit", style: UIBarButtonItem.Style.plain, target: self, action: #selector(theTransition))
}
#objc func theTransition() {
let second = self.storyboard?.instantiateViewController(withIdentifier: "ThreeNavID") as! ThreeNavigationViewController
self.present(second, animated: true, completion: nil)
}
Third Page -
override func viewDidAppear(_ animated: Bool) {
navigationItem.title = "Add New Contact"
navigationItem.rightBarButtonItem = UIBarButtonItem(title: "Done", style: UIBarButtonItem.Style.plain, target: self, action: #selector(theTransition))
}
#objc func theTransition() {
let second = self.storyboard?.instantiateViewController(withIdentifier: "NavID") as! NavigationViewController
self.present(second, animated: true, completion: nil)
}
Don't use 3 navigation controllers. Use one navigation controller like this
UITabBarController_____ Another View Controller1
|
|____ Another View Controller2
|
|____ Contact - UINavigationController -- FirstPage
UINavigationController -- FirstPage --> SecondPage --> ThirdPage
Embed the first Page in the navigation controller and push second and third pages. Don't create a new instance for the first page. Use popViewController to go to the previous view controller.
You can pass data to the next view controller with pushViewController and use custom delegate or closure to send data to the previous view controller. Or create a variable in custom navigation controller class like this
class CustomNavigationController: UINavigationController {
var name: String?
}
Then read and write data from other view controllers like this
class FirstPage: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
print((self.navigationController as? CustomNavigationController)?.name)
(self.navigationController as? CustomNavigationController)?.name = "FirstPage"
}
#objc func goToSecondPage() {
if let secondPage = self.storyboard?.instantiateViewController(withIdentifier: "SecondPage") as? SecondPage {
self.navigationController?.pushViewController(secondPage, animated: true)
}
}
#objc func goToThirdPage() {
if let thirdPage = self.storyboard?.instantiateViewController(withIdentifier: "ThirdPage") as? ThirdPage {
self.navigationController?.pushViewController(thirdPage, animated: true)
}
}
}
class SecondPage: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
print((self.navigationController as? CustomNavigationController)?.name)
(self.navigationController as? CustomNavigationController)?.name = "SecondPage"
}
#objc func goToThirdPage() {
if let thirdPage = self.storyboard?.instantiateViewController(withIdentifier: "ThirdPage") as? ThirdPage {
self.navigationController?.pushViewController(thirdPage, animated: true)
}
}
#objc func goToFirstPage() {
self.navigationController?.popViewController(animated: true)
}
}
class ThirdPage: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
print((self.navigationController as? CustomNavigationController)?.name)
(self.navigationController as? CustomNavigationController)?.name = "ThirdPage"
}
#objc func goToFirstPage() {
self.navigationController?.popToRootViewController(animated: true)
}
#objc func goToSecondPage() {
self.navigationController?.popViewController(animated: true)
}
}
I've a three screens for navigate. But in one these, I can't put a UIBarButtoItem. This screen, is for create a register, and I wants create a button to "Save", that simple. When I did, I select the Bar Button Item, the XCode do not leave me drop on the bar. And programatically, also not works.
I tried this: (Not happens)
var btSalvar : UIBarButtonItem?
override func viewDidLoad() {
super.viewDidLoad()
btSalvar = UIBarButtonItem(title: "Salver", style: .plain, target: self, action: nil)
self.navigationItem.rightBarButtonItem = btSalvar
// Do any additional setup after loading the view.
}
And in storyboard: (Note: The Button "Item" not keep fixed on the bar)
You have many options to do that, One of them is:
You have to create a super view controller and add navigation button code in it. I have added a back button for a demo:
class MainViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
}
/**
To add the left back button on navigation.
*/
var addLeftBarMenuButtonEnabled: Bool? {
didSet {
if addLeftBarMenuButtonEnabled! {
let leftBarBtn = UIButton()
leftBarBtn.setImage(UIImage(named: "backIcon"), for: .normal)
leftBarBtn.frame = CGRect(x: 0, y: 0, width: 30, height: 30)
leftBarBtn.addTarget(self, action: #selector(actionBackButton), for: .touchUpInside)
self.navigationItem.leftBarButtonItem = UIBarButtonItem.init(customView: leftBarBtn)
} else {
self.navigationItem.setHidesBackButton(true, animated: true)
}
}
}
///This is action method for back button
#objc func actionBackButton() {
self.view.endEditing(true)
self.navigationController?.popViewController(animated: true)
}
}
Now you need to use the back button in your view controller which super view controller is MainViewController:
class ViewController: MainViewController {
override func viewDidLoad() {
super.viewDidLoad()
self.addLeftBarMenuButtonEnabled = true
}
}
You can add navigation button like that and use where you want. If you want to use it in every view controller then you have to add 'self.addLeftBarMenuButtonEnabled = true' in main view controller like that
class MainViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
self.addLeftBarMenuButtonEnabled = true
}
}
iOS 10 Messages app's navigation bar increases/decreases the height when you push/pop a conversation (with a smooth transition).
Typically I make a taller custom navigation bar using sizeThatFits:, but it persists across pushes and pops of view controllers in a navigation controller.
How is it possible to have a taller navigation bar just for some view controllers across navigation sequences like the Messages app?
Thanks!
Very interesting problem. I spent some time to achieve something like this in the Messages app and that is what I've done.
Finally, I use this trick to animate navigationBar height during push/pop and also pop with swipe gesture.
UIView.beginAnimations(nil, context: nil)
self.frame = navFrame
UIView.commitAnimations()
Below you can see my implementation:
extension UINavigationBar {
func applyHeight(_ height: CGFloat, animated: Bool = true) {
var navFrame = self.frame
navFrame.size.height = height
if animated {
UIView.beginAnimations(nil, context: nil)
self.frame = navFrame
UIView.commitAnimations()
} else {
self.frame = navFrame
}
}
}
class ViewControllerA: UIViewController {
override func loadView() {
super.loadView()
title = "A"
view.backgroundColor = .blue
navigationItem.rightBarButtonItem = UIBarButtonItem(title: "NEXT", style: .plain, target: self, action: #selector(self.showController))
navigationController?.navigationBar.isTranslucent = false
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
}
func showController() {
navigationController?.pushViewController(ViewControllerB(), animated: true)
}
}
class ViewControllerB: UIViewController {
override func loadView() {
super.loadView()
title = "B"
view.backgroundColor = .red
}
override func viewWillAppear(_ animated: Bool) {
navigationController?.navigationBar.applyHeight(100)
super.viewWillAppear(animated)
}
override func willMove(toParentViewController parent: UIViewController?) {
if parent == nil { // here you know that back button was tapped
navigationController?.navigationBar.applyHeight(44)
}
super.willMove(toParentViewController: parent)
}
}
Things to improve
Title jumps to top
Jumping title is visible while you swipe to pop, but personally, I think this is a small problem :)
Hope it helps you, and maybe someone can improve this implementation. Of course, I will still try to figure out how to make this better :)
Here it's a github repository. Please use navigation_bar_height branch.
I think now you can achieve something similar with just this, just set large title to always: