First segue with 2 second delay - swift

I have a button (searchButton) in the parent view controller. The action for the button (searchButtonTapped) is to segue to Result View Controller as shown below.
#IBAction func searchButtonTapped(_ sender: Any) {
let mainStoryboard = UIStoryboard(name: "SearchResult", bundle: Bundle.main)
guard let resultViewController = mainStoryboard.instantiateViewController(withIdentifier: "ResultViewController") as? ResultViewController else { return }
resultViewController.resultText = initialText
navigationController?.pushViewController(resultViewController, animated: true)
}
When I tapped the button (searchButton) for the first time, the segue action happens but with 2 second delay. When I return to the parent view controller and tap the button again, from the second time, the segue is smooth. There is a two second delay only for the very first segue after the app is launched. What am I doing wrong to have the delay here? Your thoughts would be greatly appreciated. Thanks.
Here is the viewDidLoad for the child view controller (ResultViewController). I tried to print "A" to "F" in order to identify the step that causes delay, but as soon as I tap searchButton, All from A to F prints without delay. And then after printing "F", it delays about 2 seconds before the screen gets updated with the push segue.
override func viewDidLoad() {
super.viewDidLoad()
print("A")
populateDataArray(resultText.isEmpty)
resetSelectedItemArray(movingIn: true)
print("B")
headerView = listTableView.tableHeaderView as? ResultTableHeaderView
headerView.bannerImageView.contentMode = .scaleAspectFill
listTableView.tableHeaderView = nil
listTableView.addSubview(headerView)
listTableView.contentInset = UIEdgeInsets(top: tableHeaderViewHeight, left: 0, bottom: 0, right: 0)
print("C")
initializeHeaderView()
setupRightBarButtonItems()
setupInformationBar()
setupTableViewStyle()
print("D")
navigationItem.setHidesBackButton(true, animated: false)
let layout: UICollectionViewFlowLayout = UICollectionViewFlowLayout()
layout.minimumInteritemSpacing = 5
layout.scrollDirection = .horizontal
resultCollectionView.collectionViewLayout = layout
print("E")
resultCollectionView.delegate = self
resultCollectionView.dataSource = self
listTableView.delegate = self
listTableView.dataSource = self
stickyDropDownTableView.delegate = self
stickyDropDownTableView.dataSource = self
informationDropDownTableView.delegate = self
informationDropDownTableView.dataSource = self
print("F")
}

Usually this happens when the destination view is particularly complex and certainly require any heavy processing to load, and it takes that bit of time. But your's doesn't look like. So, You can perform your segue inside on main thread and that will ease up the problem.
DispatchQueue.main.async {
navigationController?.pushViewController(resultViewController, animated: true)
}

Related

Changing the back button of UINavigaitonBar with MVVM+C

I am using MVVM+C pattern to build my app. Currently I am facing a problem with changing the native back button title and image of navigation bar to the custom image without the title. I've tried a lots of solutions what I was able to find, but nothing set the different title or even an image. I've ended up with this code in AppDelegate.swift:
let navigationController: UINavigationController = .init()
if #available(iOS 13.0, *) {
let appearence = UINavigationBarAppearance()
appearence.configureWithOpaqueBackground()
appearence.backgroundColor = .backgroundColor
appearence.shadowColor = nil
appearence.shadowImage = nil
navigationController.navigationBar.standardAppearance = appearence
navigationController.navigationBar.scrollEdgeAppearance = navigationController.navigationBar.standardAppearance
} else {
navigationController.navigationBar.isTranslucent = false
navigationController.navigationBar.barTintColor = .backgroundColor
navigationController.navigationBar.shadowImage = nil
navigationController.navigationBar.shadowColor = nil
}
// This code is not working at all, always get "Back" as a default with default image =====
let backButtonBackgroundImage = UIImage(named: "backButton")
navigationController.navigationBar.backIndicatorImage = backButtonBackgroundImage
navigationController.navigationBar.backIndicatorTransitionMaskImage = backButtonBackgroundImage
let backBarButtton = UIBarButtonItem(title: "", style: .plain, target: nil, action: nil)
navigationController.navigationItem.backBarButtonItem = backBarButtton
// =========
navigationController.navigationBar.tintColor = .primary
window?.rootViewController = navigationController
window?.makeKeyAndVisible()
Also, I've followed the official documentation but without any success. As default I've set the navigation bar as hidden (because is not needed for multiple times) and I am showing it in ViewWillAppear and hiding in ViewWillDisappear methods.
Is there someone who has an idea of what's going on? Thanks!
The result of this code:
Expected result:
This is what I get with the new code:
SOLUTION:
After using code from Scott I was able to change the image and look of the navigation bar but I lost the ability to swipe back. After adding this code to the UINavigationBar extension I was able to get it back:
extension UINavigationController: UIGestureRecognizerDelegate {
#objc func goBack(sender: Any?) {
self.popViewController(animated: true)
}
override open func viewDidLoad() {
super.viewDidLoad()
interactivePopGestureRecognizer?.delegate = self
}
public func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool {
return viewControllers.count > 1
}
}
Below is some Playground code that shows a UINavigationController with a custom back button that is an image.
Note that what it does is hides the system provided back button, then substitutes another button that still performs the "back" action but on a custom UINavigationController.
There may be a more efficient way to duplicate the functionality of "back" that doesn't involve a custom class and a custom target-action setup, but I couldn't find one quickly so finding that solution can be left as an exercise for the reader.
import UIKit
import SwiftUI
import PlaygroundSupport
NSSetUncaughtExceptionHandler { error in
debugPrint(error)
}
class MyNavController : UINavigationController {
#objc func goBack(sender: Any?) {
self.popViewController(animated: true)
}
}
let navDestination1 = UIViewController()
navDestination1.navigationItem.title = "Destination 1"
let navigationController = MyNavController(rootViewController: navDestination1)
if #available(iOS 13.0, *) {
let appearence = UINavigationBarAppearance()
appearence.configureWithOpaqueBackground()
appearence.backgroundColor = .purple
appearence.shadowColor = nil
appearence.shadowImage = nil
navigationController.navigationBar.standardAppearance = appearence
navigationController.navigationBar.scrollEdgeAppearance = navigationController.navigationBar.standardAppearance
} else {
navigationController.navigationBar.isTranslucent = false
navigationController.navigationBar.barTintColor = .purple
navigationController.navigationBar.shadowImage = nil
}
let navDestination2 = UITableViewController()
navDestination2.navigationItem.title = "Destination 2"
navDestination2.navigationItem.hidesBackButton = true
navDestination2.navigationItem.leftBarButtonItem = UIBarButtonItem(
image: UIImage(systemName: "multiply.circle.fill"),
style: UIBarButtonItem.Style.done,
target: navigationController,
action: #selector(MyNavController.goBack))
navigationController.pushViewController(navDestination2, animated: true)
navigationController.view.bounds = CGRect(x: 0,y: 0,width: 320,height: 480)
PlaygroundSupport.PlaygroundPage.current.liveView = navigationController

Cart count not updating in navigation bar while popViewController in swift

I have created custom navigation bar with cart and its count like below code: like this if i call setUpNavigationBar() in all viewcontrollers i am getting navigationbar with cart and count
func setUpNavigationBar(){
self.navigationController?.navigationBar.backgroundColor = CommonColor.navigationColor
self.navigationController?.navigationBar.barTintColor = CommonColor.navigationColor
self.navigationController?.navigationBar.isTranslucent = false
self.navigationController?.navigationBar.layer.masksToBounds = false
let cartBtn: BadgeButton = BadgeButton(type: .custom)
cartBtn.setImage(#imageLiteral(resourceName: "icon55"), for: .normal)
cartBtn.addAction(for: .touchUpInside) {
print("in cart")
let signupVC = (StoryBoard.driver).instantiateViewController(identifier: "ShoppingCartVC") as! ShoppingCartVC
self.navigationController?.pushViewController(signupVC, animated: true)
}
cartBtn.frame = CGRect(x: 0, y: 0, width: 20, height: 20)
let cartQty = UserDefaults.standard.value(forKey: "cartCount")
cartBtn.badgeText = cartQty as? String
let btnCart = UIBarButtonItem(customView: cartBtn)
self.navigationItem.setRightBarButton(btnCart, animated: true)
}
func setBadgeCountForCart(with : String?){
let badge = self.navigationItem.rightBarButtonItems?.map({$0.customView}).filter({$0 is BadgeButton}).first as? BadgeButton
badge?.badgeText = with
}
but in ShoppingCartVC i don't want navigation bar so, i didn't call setUpNavigationBar in ShoppingCartVC viewdidload
so if remove product from cart and i click back button in ShoppingCartVC then the cartConut is not updating in poped viewcontroller
after remove product i have updated cartCount like below:
class ShoppingCartVC: UIViewController {
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(true)
self.navigationController?.navigationBar.isHidden = true
self.serviceCall()
}
func serviceCall(){
self.cartDB = ShoppingCartDetaModel(dictionary: responseData.dict as NSDictionary? ?? NSDictionary())
UserDefaults.standard.set(self.cartDB?.result?.cart?.total_item, forKey: "cartCount")
self.setUpNavigationBar()
}
#IBAction func back(_ sender: UIButton){
self.navigationController?.navigationBar.isHidden = false
self.serviceCall()
UserDefaults.standard.set(self.cartDB?.result?.cart?.total_item, forKey: "cartCount")
self.setUpNavigationBar()
self.navigationController?.popViewController(animated: true)
}
}
where should i change, please do help
You need to update the BadgeButton its text property with the updated quantity after you navigated back. You can use different methods for achieving that. The easiest and less clean one is to call self.setUpNavigationBar() in the viewWillAppear method. A cleaner way is to use the NotificationCenter class or a Publisher and post an update every time the quantity changes.
ViewWillAppear: https://developer.apple.com/documentation/uikit/uiviewcontroller/1621510-viewwillappear
NotificationCenter: https://developer.apple.com/documentation/foundation/notificationcenter
Publisher: https://developer.apple.com/documentation/combine/publisher

Displaying a modal view controller with transparent view above another controller while displaying navigation bar of the parent controller in Swift

I would like to display a modal view controller with transparent background which has a small view inside, which will appear as an alert. I want to display the same above another view controller which is in a navigation stack. I tried to present my second controller in many ways like getting embedded in navigation controller, from navigation controller , second view controller itself etc . But any of these doesn't give me the same navigation bar as the parent controller. I tried adding it as a subview, but then my textfield delegate methods are not getting called. Could anyone please help me with a solution for this. Adding some of the solutions that I tried which I got from different stack overflow answers..
1.
guard let alertController = /* my controller */ else { return }
let navController = UINavigationController(rootViewController: alertController)
alertController.delegate = self
alertController.valueText = "Value"
alertController.userPhoneNumber = updatedUserPhoneNumber
navController.modalTransitionStyle = .crossDissolve
navController.modalPresentationStyle = .currentContext
present(navController, animated: false, completion: nil)
2.
guard let alertController = /* my controller */ else { return }
alertController.delegate = self
alertController.valueText = "Value"
alertController.userPhoneNumber = updatedUserPhoneNumber
alertController.modalTransitionStyle = .crossDissolve
alertController.modalPresentationStyle = .currentContext
navigationController.present(alertController, animated: false, completion: nil)
3.
guard let alertController = /* my controller */ else { return }
let navController = UINavigationController(rootViewController: alertController)
alertController.delegate = self
alertController.valueText = "Value"
alertController.userPhoneNumber = updatedUserPhoneNumber
navController.modalTransitionStyle = .crossDissolve
navController.modalPresentationStyle = .currentContext
navigationController.present(navController, animated: false, completion: nil)
4.
guard let alertController = /* my controller */ else { return }
alertController.delegate = self
alertController.valueText = "Value"
alertController.userPhoneNumber = updatedUserPhoneNumber
alertController.modalTransitionStyle = .crossDissolve
alertController.modalPresentationStyle = .currentContext
present(alertController, animated: false, completion: nil)
Thank you very much in advance...
I did it in a project like this :
let controllerToPresent = UIViewController()
controllerToPresent.providesPresentationContextTransitionStyle = true
controllerToPresent.definesPresentationContext = true
controllerToPresent.modalPresentationStyle = .overCurrentContext
controllerToPresent.view.backgroundColor = UIColor.init(white: 0.4, alpha: 0.8)
navigationController.visibleViewController?.present(controllerToPresent, animated: true)
// OR
navigationController.present(controllerToPresent, animated: true)
I hope it can help you.
I didn't get an idea on how I can present the second controller's view. But finally I added it as a subview only. And how I got it worked is like below
https://developer.apple.com/library/archive/featuredarticles/ViewControllerPGforiPhoneOS/ImplementingaContainerViewController.html
addChild(alertController)
view.addSubview(alertController.view)
alertController.didMove(toParent: self)
and removing the same like
willMove(toParent: nil)
view.removeFromSuperview()
removeFromParent()
Please advise me if any other solution works.

Change ViewController from Tabbar

I have a UITabBarController with 3 tabs and a SettingsVC where you can choose the appearance of MainVC (There are 2 different ViewControllers which based on what user prefers should be shown as the MainVC).
I made the application and it works but it's really buggy, cause I was just pushing the ViewControllers on top of each other, and in some certain conditions you see bugs while switching tabs.
Main.storyboard
Here is some part of my code(after trying a lot of stuff) which changes the VC's based on UserDefaults
CheckSettingsStatus.swift
//
// Check for Double Column Settings and navigate to Double Column VC
//
func checkMainVCViewStatusAndNavigateAcordingly(){
if UserDefaults.standard.integer(forKey: KEY_MAIN_VC_VIEW) == 1{
//let appDel = UIApplication.shared.delegate as! AppDelegate
//appDel.goToDoubleMainVC()
let mainVC = storyboard?.instantiateViewController(withIdentifier: "DoubleColumnMainVC") as! DoubleColumnMainVC
self.navigationController?.viewControllers[0] = mainVC
//navigationController?.pushViewController(mainVC, animated: false)
var navigationArray = self.navigationController?.viewControllers
print("Number of VCs = \(String(describing: navigationArray?.count))")
if navigationArray?.count == nil{
self.navigationController?.pushViewController(mainVC, animated: false)
}else{
self.navigationController?.popToRootViewController(animated: false)
}
// self.navigationController?.viewControllers = navigationArray!
}
}
//
// same as function above but goes to MainVC
//
func checkDoubleColumnMainVCViewStatusAndNavigateAcordingly(){
if UserDefaults.standard.integer(forKey: KEY_MAIN_VC_VIEW) == 0{
// let appDel = UIApplication.shared.delegate as! AppDelegate
// appDel.goToMainVC()
let mainVC = storyboard?.instantiateViewController(withIdentifier: "MainVC") as! MainVC
self.navigationController?.viewControllers[0] = mainVC
var navigationArray = self.navigationController?.viewControllers
print("Number of VCs = \(String(describing: navigationArray?.count))")
if navigationArray?.count == nil{
navigationController?.pushViewController(mainVC, animated: false)
}else{
self.navigationController?.popToRootViewController(animated: false)
}
// self.navigationController?.viewControllers = navigationArray!
}
}
With this in ViewWillAppear of both this Controllers I call these functions to switch accordingly.
Any ideas what I'm doing wrong? Can I push controllers from UITabBarController and not NavigationController? something like:
tabBarController.pushViewController(mainVC, animated: false)
instead of:
navigationController.pushViewController(mainVC, animated: false)
Thank you in advance
UPDATE:
In my best case when everything works nicely, on first launch of the app (to the second mainVC) the Buttons in NavBar doesn't work.
EDIT:
I just realised that the NavigationBar that I'm seeing above my SecondMainVC is the NavigationBar from MainVC, that is why the buttons are not working. how is that possible?
I faced same issue while trying to develop a tab bar controller in storyboard , for this reason I found that the best way is to implement it by code.
so you can make your code look like this :
1) create a a sub class of UITabController and let it conform to UITabBarControllerDelegate
class TabBarVC: UITabBarController , UITabBarControllerDelegate {
2)then write this method in your class to initialize the tabs in your project:
private func settingUpTabBarAndInitializingViewControllers(){
//self delagting
self.delegate = self
//home tab
let homeStoryboard = UIStoryboard(name: Constants.StoryBoards.homeStoryboard, bundle: nil)
let homeVC = homeStoryboard.instantiateViewController(withIdentifier: Constants.ControllersIDs.homeScreen) as! HomeVC
// shop tab
let shopStoybaord = UIStoryboard(name: Constants.StoryBoards.shopStoryboard, bundle: nil)
let shopVC = shopStoybaord.instantiateViewController(withIdentifier: Constants.ControllersIDs.shopScreen) as! ShopVC
//offers tab
let offersStoryboard = UIStoryboard(name: Constants.StoryBoards.offersStoryboard, bundle: nil)
let offersVC = offersStoryboard.instantiateViewController(withIdentifier: Constants.ControllersIDs.offersScreen) as! OffersVC
//more tab
let moreStoryboard = UIStoryboard(name: Constants.StoryBoards.MoreScreenStoryboard, bundle: nil)
let moreVC = moreStoryboard.instantiateViewController(withIdentifier: Constants.ControllersIDs.moreVCScreen) as! MoreOptionsTVC
//setting VCs
self.viewControllers = [homeVC , shopVC , offersVC , moreVC]
//setting buttons
//buttons images
let homeImage = UIImage(named: "TabHome")
let shopImage = UIImage(named : "TabShop")
let offerImage = UIImage(named: "TabOffers")
let moreImage = UIImage(named: "TabMenu")
// buttons shakhsiyan
let homeButton = UITabBarItem(title: homeText, image: homeImage, selectedImage: homeTappedImage)
let shopButton = UITabBarItem(title: shopText, image: shopImage, selectedImage: shopTappedImage)
let offersButton = UITabBarItem(title: offersText, image: offerImage, selectedImage: offersTappedImage)
let moreButton = UITabBarItem(title: moreText, image: moreImage, selectedImage: moreTappedImage)
homeVC.tabBarItem = homeButton
shopVC.tabBarItem = shopButton
offersVC.tabBarItem = offersButton
moreVC.tabBarItem = moreButton
}
P.S. you can add tabs as much as you want and while scrolling in them you wont face any bug,
make sure to have only one navigation controller before your tab bar controller.
Check this hope it helps
Ok you can create a DEMO to see how thing works.After that you can implement it in your project.I know it may not look good to you but read it once and see image attached i am sure will get your answer.
1.I have created two UIViewControllers with the UITabBarController.
2.I have created class ItemFirstVC,ItemTwoVC and assigned to UIViewControllers in storyboard
3.Each controller is embedded in UINavigationController
4.Create a custom class of UITabBarController
5.Assign it to UITabBarController in storyboard
6.Now Each UIViewController has a button named as Firs Button,Second Button
7.From each button i have connected Action Segue to one more controller as you can see in image.
8.Now i have created a third UIViewController which is separate from UITabBarController.
9.Now we will load that Third UIViewController in UITabBarController
10.Create one more class ItemThirdVC and assign it to third UIViewController.
11.It also has a button named as third button action segued to one more UIViewController
12.Now switch to custom class of UITabBarContoller
// variable required to see work in action
var count = 0
class CustomTabBarController: UITabBarController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
// remember to assign delegate
delegate = self
}
// this delegate indicates whether this tab should be selected or not
extension CustomTabBarController : UITabBarControllerDelegate{
func tabBarController(_ tabBarController: UITabBarController, shouldSelect viewController: UIViewController) -> Bool {
// switch to UIViewController tabs multiple times
count = count + 1
if(count == 5){
// when count reaches 5 a new controller will load
let navigationController = viewController as? UINavigationController
guard navigationController != nil else {return true}
let viewController = storyboard?.instantiateViewController(withIdentifier: "ItemThreeVC")
guard let vc = viewController else {return true}
navigationController?.setViewControllers([vc], animated: false)
}
return true
}
}

How to load popover in swift

I am trying to get a popover working in swift.
The view i am trying to put into the popover is in its own separate xib.
The code to load is below
let view = OrganisationDetails()
view.modalPresentationStyle = .Popover
let popoverMenuViewController = view.popoverPresentationController
view.preferredContentSize = CGSizeMake(550,550)
popoverMenuViewController?.permittedArrowDirections = .Any
popoverMenuViewController?.delegate = self
popoverMenuViewController?.sourceView = sender
popoverMenuViewController?.sourceRect = CGRect(x: 1, y: 1, width: 60, height: 60)
presentViewController(view, animated: true, completion: nil)
What is happening atm is the popover is loading totally blank and not displaying the view.
Any ideas what I'm doing wrong
Thanks
Actually it is much simpler than that. In the storyboard you should make the viewcontroller you want to use as popover and make a viewcontroller class for it as usual. Make a segue as shown below from the object you want to open the popover, in this case the UIBarButton named "Config".
In the "mother viewcontroller" implement the "UIPopoverPresentationControllerDelegate" and the delegate method:
func popoverPresentationControllerDidDismissPopover(popoverPresentationController: UIPopoverPresentationController) {
//do som stuff from the popover
}
Override the "prepareForSeque" method like this:
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
//segue for the popover configuration window
if segue.identifier == "yourSegueIdentifierForPopOver" {
if let controller = segue.destinationViewController as? UIViewController {
controller.popoverPresentationController!.delegate = self
controller.preferredContentSize = CGSize(width: 320, height: 186)
}
}
}
And your done. And you can now treat the popover view as any other view, ie. add fields and what not!
This is how I do it
//MARK:idPopupFileTable
let popoverVC = storyboard?.instantiateViewControllerWithIdentifier("idPopupFileTable") as UIViewController
popoverVC.modalPresentationStyle = .Popover
popoverVC.preferredContentSize = CGSizeMake(300, 200)
if let popoverController = popoverVC.popoverPresentationController {
popoverController.sourceView = sender
popoverController.sourceRect = sender.bounds
popoverController.permittedArrowDirections = .Any
popoverController.delegate = self
}
presentViewController(popoverVC, animated: true, completion: nil)
}