Changes to navigationItem displayed with delay - ios

I'm facing a delay when changing properties of a navigationItem inside a ViewController which is embedded in a NavigationController.
Consider the example Master-Detail-App provided by XCode: It contains a MasterViewController with a segue to a DetailViewController. I'm now trying to customize the navigation bar inside the viewDidLoad method of the DetailViewController.
class DetailViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
//delay if the button (or any other property) is set here
navigationItem.rightBarButtonItem = editButtonItem
}
}
The changes to the navigation bar will be displayed but with a noticeable delay. However, if the changes to the navigation bar are made from within the MasterViewController everything works perfectly fine and smooth:
class MasterViewController: UIViewController {
...
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "showDetail" {
let controller = (segue.destination as! UINavigationController).topViewController as! DetailViewController
//no delay if the button would instead be set here
//navigationItem.rightBarButtonItem = editButtonItem
}
}
}
To me it seems not to be a good practice to modify items of the navigationBar from within the MasterViewController. Especially because
items like the title and buttons heavily depend on the DetailViewController.
What is now the most preferable way to change the navigationItem without delay and with separation of concern in mind?

Add code to viewWillApear instead of viewDidLoad
override func viewWillApear() {
super.viewWillApear()
navigationItem.rightBarButtonItem = editButtonItem
}

Related

Dynamically Changing Theme of a view controller from another view controller without reloading

I have a "ViewControllerA" [VCA] and a view that slides into view when swiped (A hamburger menu?). It's ViewController is "ViewControllerB"[VCB]
I use this library to implement the side drawer
https://github.com/dekatotoro/SlideMenuControllerSwift
What I want to achieve is that upon clicking on the button on VCB (after it slides onto VCA) the theme of the pages change. So I want update the colours of multiple elements.
How do I change the colours of elements in VCA without dismissing/reloading either controllers?
Some screenshots
The simplest way to achieve your desired result you can go with NotificationCenter
Add an observer to the ViewController where you would like to get the event of theme changed (in your case it will be A)
NotificationCenter.default.addObserver(self, selector: #selector(self.themeChanged), name: "your_notification_name", object: nil)
Add a method to handle the event of a post notification
#objc func self.themeChanged(notification: NSNotification){
//Your code
}
Post the notification once you taped on the theme change button from another ViewController (in your case it will be B)
NotificationCenter.default.post(name: Notification.Name("your_notification_name"), object: OBJ_TO_BE_SEND)
IMP : Please don't forget to remove the observer if it keeps adding
This framework is making use of embedded view controllers. What you need to do is add a property called VCA of type ViewControllerA to ViewControllerB's class:
class ViewControllerB: UIViewController {
var VCA: ViewControllerA!
}
Then, in ViewControllerA, you need to implement prepare(for segue:) so that, when ViewControllerB is initialized, you set its VCA property so that ViewControllerA is stored as ViewControllerB's VCA.
class ViewControllerA: UIViewController {
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let VCB = segue.destination as? ViewControllerB {
VCB.VCA = self
}
}
}
Then, whenever clicking the theme button on ViewControllerB, you need to reference VCA and change its properties:
class ViewControllerB: UIViewController {
var VCA: ViewControllerA!
#IBAction func themeButtonPressed(_ sender: Any?) {
if UserDefaults.standard.bool(forKey: "DarkThemeOn") {
//Set the background color to light for both the menu VC and VCA
view.backgroundColor = .white
VCA.view.backgroundColor = .white
} else {
//Set the background color to dark for both the menu VC and VCA
view.backgroundColor = .black
VCA.view.backgroundColor = .black
}
//Toggle the theme
UserDefaults.standard.set(!UserDefaults.standard.bool(forKey: "DarkThemeOn"), forKey: "DarkThemeOn")
}
}

Bar button action on ViewController in ViewContainer

I have a container embedded in a ViewController with a navigation bar. The ContainerView contains another ViewController with some textfields.
When the ContainerView is first displayed, the textfields are disabled.
What I would like to do is add an edit button to the navigation bar which enables the textfields.
Basically, is it possible for a bar button item in a viewcontroller to have an action on another view controller displayed in a container?
in this case you can do a little trick
the bar button will be in the ViewController but we can store the content of the ContainerView in a variable in the first ViewController
to do that i would suggest to make the content of the ContainerView has a custom class so you would write a function in it
here is an example on how to catch the content of the ContainerView
class ViewController: UIViewController{
weak var containter: ContainerViewController!
#IBAction func menuAction(_ sender: UIBarButtonItem) {
//do what you want containter.doAction()
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?){
if let vc = segue.destination as? ContainerViewController {
self.containter = vc
}
}
}
Always the case. I've been looking for an answer for hours, as soon as I ask I figure it out.
I'm not sure its the best way to do it but I just made an edit bool controller by the edit button. Then used:
var child = self.childViewControllers[0]
child.viewWillAppear(false)
This reloaded the data and if it was in edit mode enabled the textfields. Works fine!

How to perform segue from container view within a view displayed by navigation controller?

I want to segue from a view container within "H" that is presented using the navigation controller connected to the Split View Controller. How can I accomplish this? I have tried regular performSegueWithIdentifier using locally linked storyboard ID's but that removes the top navigation bar. I want to retain the top navigation bar and execute the segue as if it was done using the master navigation controller (rows that select which view controller is being presented in the detail view).
Any help is greatly appreciated!
Here is an example of how to perform a segue from an embedded ViewController.
ViewController.swift
import UIKit
protocol SegueHandler: class {
func segueToNext(identifier: String)
}
class ViewController: UIViewController, SegueHandler {
func segueToNext(identifier: String) {
self.performSegueWithIdentifier(identifier, sender: self)
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "EmbedH" {
let dvc = segue.destinationViewController as! HViewController
dvc.delegate = self
}
}
}
HViewController.swift
import UIKit
class HViewController: UIViewController {
weak var delegate: SegueHandler?
#IBAction func pressH(sender: UIButton) {
delegate?.segueToNext("GoToGreen")
}
}
Setup:
Use delegation to have the HViewController tell its embedding viewController to perform the segue.
Create a protocol called SegueHandler which just describes a class that implements the method segueToNext(identifier: String).
protocol SegueHandler: class {
func segueToNext(identifier: String)
}
Make your viewController implement this protocol by adding it to the class declaration line:
class ViewController: UIViewController, SegueHandler {
and by implementing the required function.
Add a delegate property to HViewController:
weak var delegate: SegueHandler?
Click on the embed segue arrow between ViewController and HViewController. Give it the identifier "EmbedH" in the Attributes Inspector.
Create a show segue between ViewController and the GreenViewController by Control dragging from the viewController icon at the top of ViewController to the GreenViewController. Name this segue "GoToGreen" in the Attributes Inspector.
In prepareForSegue for ViewController, when the "EmbedH" segue happens, set the delegate property of HViewController to self (ViewController).
When the user clicks the H button in the HViewController, call delegate?.segueToNext("GoToGreen") to trigger the segue in ViewController.
Here it is running in the simulator:
I was needing exactly what #vacawama proposed here, though I couldn't reproduce that, I tried exactly your steps but self.delegate?.segueToNext("GoToGreen") got called but neither the protocol itself nor the container view controller. After an entire day searching about this approach I realized the problem was with the swift version. Just replace this:
func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "EmbedH" {
let dvc = segue.destination as! HViewController
dvc.delegate = self
}
}
for this:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "EmbedH" {
let dvc = segue.destination as! HViewController
dvc.delegate = self
}
}
Other detail I was missing was about the embedded segue. Be sure to connect the container View to the HViewController, not the View Controller itself, otherwise the Embed option for segue won't appear.

The NavigationItem's TintColor can not be set

My Situation:
There are a View_A (UICollectionViewController&UICollectionViewCell) and View_B (UIViewController). I want to switch to the View_B when I touched one of cell in the View_A with Segue in the StoryBoard.
In the StoryBoard, I connected View_A and View_B with the Push Segue which identifier is SegueToView_B.
And the function of switchViews just worked fine.
My Problem:
With the Push Segue, I do not need to add a BackButton (NavigationItem) to turn back to the View_A, because there is a 'NavigationItem' be crated automatically by system. And I tried other type segues, like Modal, Popover, and the NavigationItem was not created automatically. I want to ask why?
I want to set the specific color, not the default blue, for that NavigationItem which be created by system automatically, but I failed to find it. After that I just set the color in the prepareForSegue(), but it did not work. Please tell how to set the specific color for it?
My Code:
override func collectionView(collectionView: UICollectionView, didSelectItemAtIndexPath indexPath: NSIndexPath) {
self.collectionView?.setPresenting(true, animated: true, completion: nil)
let delegate = UIApplication.sharedApplication().delegate as! AppDelegate
self.selectedCard = delegate.otherCards[indexPath.row]
self.performSegueWithIdentifier("SegueToView_B", sender: self)
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if let identifier = segue.identifier {
if identifier == "SegueToView_B" {
let myOtherCardViewController = segue.destinationViewController as? View_BViewController
myOtherCardViewController!.otherCard = self.selectedCard
myOtherCardViewController!.navigationItem.backBarButtonItem?.tintColor = UIColor.whiteColor() // Failed to work!!!
myOtherCardViewController?.navigationItem.leftBarButtonItem?.tintColor = UIColor.whiteColor() // Failed to work, too!!!
}
}
}
Thanks for your help.
Ethan Joe
To set the tintColor for that navigation bar:
myOtherCardViewController.navigationBar.tintColor = .whiteColor()
Why there is no Navigationbar when you use the Modal or PopOver? Because thats how Modal and Popover work! You have to create another Navigation controller for the view you are connecting with the Modal segue, like this:
Another technique I am using is, to create a single NavigationController class, and set all the desired properties (color, font etc.) and then link all the NavigationControllers in the Storyboard to that NavigationController class.
With that you wont have to reconfigure every NavigationController.
Your Solution
You can set back button hidden in View_B controller in viewDidLoad method like this.
class View_BViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
self.navigationItem.hidesBackButton = true;
// Do any additional setup after loading the view.
}
}
To set tint color, you have to create subclass of UINavigationController, and assign that class to your UINavigationController in UIStoryboard
You subclass will look like this, to set tint color,
class navigationController: UINavigationController {
override func viewDidLoad() {
super.viewDidLoad()
//self.navigationBar.barStyle = UIBarStyle.Default
self.navigationBar.tintColor = UIColor.redColor()
// Do any additional setup after loading the view.
}
//Other stuff
}
May this help you!!

iOS Storyboard: Not able to instantiate view controller properly

I'm really having trouble instantiating a custom view controller.
This is my storyboard set up. The third view controller is the one I'm trying to present.
I've tried both of these methods.
1: This results in black screen.
var searchController: SearchController = SearchController()
self.presentViewController(searchController, animated: true, completion: nil)
2: This results in a white, empty view controller popping up.
let mainStoryboard = UIStoryboard(name: "Main", bundle: NSBundle.mainBundle())
let searchController : UIViewController = mainStoryboard.instantiateViewControllerWithIdentifier("searchController") as! UIViewController
self.presentViewController(searchController, animated: true, completion: nil)
Here is the code for the actual view controller:
class SearchController: UIViewController {
lazy var searchBar: UISearchBar = UISearchBar(frame: CGRectMake(0, 0, 200, 20))
override func viewDidLoad() {
super.viewDidLoad()
var leftItem = (UIBarButtonItem)(customView: searchBar)
self.title = "Search"
self.navigationItem.leftBarButtonItem = leftItem
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
}
It's very confusing because another custom view controller I am is presented properly while using method #1 above. Why wouldn't it work for this controller?
Thanks a lot everyone.
When using Storyboards you don't need to use presentViewController or instantiate your view controllers manually. Instead it's best to use a segue to move from one UIViewController to the next.
1. In your case, you want a show segue, which it looks like you've already done judging by your storyboard.
2. You need to give your segue an Identifier which you can do by selecting your segue and going to the Attributes Editor.
3. To perform your segue just call the following from your first view controller (instead of presentViewController).
self.performSegueWithIdentifier("YourIdHere", sender: self)
This will cause the storyboard to instantiate your SearchViewController and present it in the way that you've defined for that segue.
4. If you want to pass any values to your SearchViewController, you can override prepareForSegue in UIViewController. In your first view controller:
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if let searchViewController = segue.destinationViewController where segue.identifier == "YourIdHere") {
// Now you can set variables you have access to in `searchViewController`.
}
}
And that should be it!

Resources