Were to call method to change appearance of navigation bar? Swift 3 - ios

I have several viewcontrollers embedded in a UINavigationController. I would like to customize the appearance of the Navigation Bar title for each viewController. What is the best method where to call setCustomTitleInNavBar. If it is called in viewDidLoad, self is not yet initialized and the app will crash. In ViewWillAppear title is not yet displayed when view is shown to user. Please advise alternative implementation if this is not the correct way to do it.
class CustomMethods {
func setCustomTitleInNavBar(textValue:String, VC:UIViewController) -> UIView {
let titleLabel = UILabel(frame: CGRect(x: 0, y: 0, width: 40, height: 40))
titleLabel.text = textValue
titleLabel.adjustsFontSizeToFitWidth = true
titleLabel.textAlignment = NSTextAlignment.center
VC.navigationItem.titleView = titleLabel
return VC.navigationItem.titleView!
}
}
//call method on the current view controller to modify the nav bar title
class someViewController: UIViewController {
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(true)
setCustomTitleInNavBar(textValue: "Where to come?", VC: self)
}
}

Here is a way to implement it through protocol :
// Protocol
protocol NavigationBarSetUpProtocol: class {
// Add more param if needed
func setupNavigationBar(with title: String)
}
// Default implemention
extension NavigationBarSetUpProtocol where Self: UIViewController {
// Default implementation
func setupNavigationBar(with title: String) {
// configure you VC navigation item with : self.navigationItem.titleView = ...
}
}
// VC A
class ViewControllerA: UIViewController, NavigationBarSetUpProtocol {
override func viewDidLoad() {
super.viewDidLoad()
setupNavigationBar(with: "HOME")
}
}
// VC B
class ViewControllerB: UIViewController, NavigationBarSetUpProtocol {
override func viewDidLoad() {
super.viewDidLoad()
setupNavigationBar(with: "PROFILE")
}
}

You can call
navigationItem.title = "Your title"
in viewDidLoad.

Related

delegate method not getting called with UITabBarController

In FourthViewController, I have a slider, which has values ranging from 1 to 1000. The value that is set gets sent via the delegate to PatternViewController, where it should be used to do sth (I put the print for testing purposes).
I've worked with delegates before and it was all ok, checked the code multiple times and multiple answers here on stack, I can't seem to find the issue. Any help would be much appreciated
update: I have added a button so that it would be easier to track along. It turns out that by pressing first time the button, nothing happens. but if I first checkout the PatternViewController, then I go back to FourthViewController and press the button, the delegate gets triggered. anyone got any idea on why is this happening?
FourthViewController
import UIKit
class FourthViewController: UIViewController {
//MARK: Outlets
#IBOutlet var persistenceButton: UIButton!
#IBOutlet var persistenceSlider: UISlider!
#IBOutlet var persistenceLabel: UILabel!
weak var delegate: FourthViewControllerDelegate?
//MARK: Stored Properties - Constants
let userDefaults = UserDefaults.standard
let keyName = "sliderValue"
//MARK: Initializer
override func viewDidLoad() {
super.viewDidLoad()
loadSliderValue()
initialSetUp()
}
//MARK: Actions
#IBAction func handleValueChanged(_ sender: UISlider) {
updateLabel()
persistSliderValue(value: persistenceSlider.value, key: keyName)
}
//MARK: Methods
func updateLabel() {
persistenceLabel.text = String(format: "%.2f", persistenceSlider.value)
}
func persistSliderValue(value: Float, key: String) {
userDefaults.set(value, forKey: key)
}
func loadSliderValue() {
let persistedValue = userDefaults.float(forKey: keyName)
persistenceSlider.value = persistedValue
updateLabel()
}
}
func initialSetUp() {
persistenceButton.addTarget(self, action: #selector(handleButtonPressed), for: .touchUpInside)
}
#objc func handleButtonPressed() {
delegate?.valueChanged(value: persistenceSlider.value)
}
}
PatternViewController
import UIKit
class PatternViewController: UIViewController, FourthViewControllerDelegate {
override func viewDidLoad() {
super.viewDidLoad()
setUp()
}
func setUp() {
if let tabBar = self.tabBarController, let viewController = tabBar.viewControllers, let fourthViewController = viewController[3] as? FourthViewController {
fourthViewController.delegate = self
}
}
func valueChanged(value: Float) {
print(value)
}
}
It depends upon how you instantiated the tab view controller. If you do it with storyboards, for example, the view controllers for the respective tabs are instantiated lazily, only instantiated as the user taps on them. (This helps reduce latency resulting from instantiating all four of the tabs’ view controllers.)
While you theoretically could go ahead and have the tab bar controller instantiate the four view controllers programmatically up front, rather than just-in-time via the storyboard, I might instead consider specifying a UITabBarControllerDelegate for the tab bar controller. Have the tab bar controller’s delegate method update the relevant tab’s view controller’s model.
Here is an example with two tabs, the first has a slider and the second has a label that displays the slider’s value. In this simplified example, I’ve moved the model object (the value associated with the slider) into the tab bar controller, and it passes it to the second view controller when you select the associated tab.
// TabViewController.swift
import UIKit
class TabBarController: UITabBarController {
var value: Float = 0.5
override func viewDidLoad() {
super.viewDidLoad()
delegate = self
}
}
// MARK: - UITabBarControllerDelegate
extension TabViewController: UITabBarControllerDelegate {
func tabBarController(_ tabBarController: UITabBarController, didSelect viewController: UIViewController) {
guard let viewController = viewController as? SecondViewController else { return }
viewController.value = value
}
}
And
// FirstViewController.swift
import UIKit
class FirstViewController: UIViewController {
#IBOutlet weak var slider: UISlider!
override func viewDidLoad() {
super.viewDidLoad()
guard let tabBarController = tabBarController as? TabViewController else { return }
slider.value = tabBarController.value
}
#IBAction func didAdjustSlider(_ sender: UISlider) {
guard let tabBarController = tabBarController as? TabViewController else { return }
tabBarController.value = sender.value
}
}
And
// SecondViewController.swift
import UIKit
class SecondViewController: UIViewController {
#IBOutlet weak var label: UILabel!
var value: Float = 0 { didSet { updateLabel() } }
let formatter: NumberFormatter = {
let formatter = NumberFormatter()
formatter.numberStyle = .percent
return formatter
}()
override func viewDidLoad() {
super.viewDidLoad()
updateLabel()
}
func updateLabel() {
label?.text = formatter.string(for: value)
}
}
Probably needless to say, I not only set the base view controller class for the two tab’s view controllers, but also set the base class for the tab bar controller’s storyboard scene to the above TabBarController.

Why does a UIBarButtonItem action not trigger in a separate class?

I wrote a class that shall handle UIBarButtonItem taps.
The initializer takes a reference to an UINavigationItem. All buttons etc. are attached to this UINavigationItem. I tried to connect them with actions (didPressMenuItem()), but when I click the button, the action is not triggered (nothing is written to the console nor the breakpoint I set is triggered).
How can I link the UIBarButtonItem to the function defined in this class?
internal final class NavigationBarHandler {
// MARK: Properties
private final var navigationItem: UINavigationItem?
// MARK: Initializers
required init(navigationItem: UINavigationItem?) {
self.navigationItem = navigationItem
}
internal final func setupNavigationBar() {
if let navigationItem = navigationItem {
let menuImage = UIImage(named: "menu")?.withRenderingMode(.alwaysTemplate)
let menuItem = UIBarButtonItem(image: menuImage, style: .plain, target: self, action: #selector(didPressMenuItem(sender:)))
menuItem.tintColor = .white
navigationItem.leftBarButtonItem = menuItem
}
}
#objc func didPressMenuItem(sender: UIBarButtonItem) {
print("pressed")
}
}
This is what happens in the view controller to which navigationItem the buttons etc. are attached.
class ContactsController: UIViewController {
// MARK: View Life Cycle
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .red
self.title = "Kontakte"
let navigationBarHandler = NavigationBarHandler(navigationItem: self.navigationItem)
navigationBarHandler.setupNavigationBar()
}
}
Th problem here is that you're instantiating NavigationBarHandler inside viewDidload() which is why the memory reference dies after viewDidLoad() finishes. What you should do is to create the variable outside like this.
class ContactsController: UIViewController {
var navigationBarHandler: NavigationBarHandler!
// MARK: View Life Cycle
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .red
self.title = "Kontakte"
self.navigationBarHandler = NavigationBarHandler(navigationItem: self.navigationItem)
navigationBarHandler.setupNavigationBar()
}
}
This way the memory reference stays.

How do I set self as delegate?

When the user pushes the button in ViewController1, I want it to call a function in ViewController2. I think the only thing I'm missing is assigning ViewController2 as its own delegate, but how do you declare self as delegate?
ViewController1
protocol VC1Delegate {
func pushThisButton(_ sender: UIButton)
}
class ViewController1: UIViewController {
var delegate: VC1Delegate!
var theButton = UIButton()
override func viewDidLoad() {
super.viewDidLoad()
buildButton()
}
func pushButton(_ sender: UIButton) {
delegate.pushThisButton(theButton)
}
func buildButton() {
theButton = UIButton(frame: CGRect(x: 100, y: 100, width: 200, height: 50))
theButton.backgroundColor = UIColor.black
theButton.addTarget(self, action: #selector(pushButton), for: .touchUpInside)
self.view.addSubview(theButton)
}
}
View Controller 2
class ViewController2: UIViewController, VC1Delegate {
// I'm guessing somewhere here I need to declare myself as the delegate?
override func viewDidLoad() {
super.viewDidLoad()
}
func pushThisButton(_ sender: UIButton) {
print("Damnit, Jack! Push the damn button!")
}
}
When you instantiate VC2, you should assign delegate to it.
For example:
protocol VC1Delegate: class {
func pushThisButton(_ sender: UIButton)
}
class ViewController1: UIViewController {
weak var delegate: VC1Delegate?
...
let vc2 = ViewController2()
self.delegate = vc2 // we are in the VC1 context
self.navigationController.pushViewController(vc2, animated: true)

Swift Delegate not being called to close a UIViewController

I have a CenterViewController which contains a Game Controller. I want to add/remove a RulesViewController that the user can easily refer to as they play.
The RulesViewController appears and is dismissed fine. But the delegate.continueGame method is never called. I've added the protocol to RulesViewController. I've added a class extension to CenterViewController to handle the delegate. What am I missing?? Any help much appreciated...
Class CenterViewController: UIViewController {
private var controller: GameController
required init(coder aDecoder: NSCoder){
controller = GameController()
}
override func viewDidLoad() {
// add all the views here
let gameView = UIView(frame: CGRectMake(0,0, ScreenWidth, ScreenHeight))
self.view.addSubview(gameView)
controller.gameView = gameView
}
// method called when rules button on the gameView is pressed
func showRulesForLevel () {
let rulesViewController = storyboard!.instantiateViewControllerWithIdentifier("RulesViewController") as! RulesViewController
presentViewController(rulesViewController, animated: true, completion: nil)
// extension to the Class to handle the delegate
extension CenterViewController: RulesViewControllerDelegate {
//func to continue the game
func continueGame() {
controller.gameView.userInteractionEnabled = true
}
}
In the RulesViewController I have:
protocol RulesViewControllerDelegate {
func continueGame()
}
class RulesViewController: UIViewController {
var delegate: RulesViewControllerDelegate?
override func viewDidLoad() {
super.viewDidLoad()
// code to add a continue button which when pressed calls continueGameAction method
}
func continueGameAction() {
// dismiss the UIViewController so game can continue
self.dismissViewControllerAnimated(true, completion: nil)
// continue the game in CenterViewController
delegate?.continueGame()
}
}
BUT delegate?.continueGame() is never called.
Ok so you need to set the delegate in showRulesForLevel method like this:
rulesViewController.delegate = self
:)

Changing the layout of an AlertController

I have used the following code to try and change the layout of a UIAlertController using a nib however the dialog just shows up and looks the same each time regardless of the nib specified, it looks like a translucent grey box, at the bottom of my screen.
class AlertDialogViewController: UIViewController {
var message: String = ""
override init() {
super.init(nibName: "SignUpViewController", bundle: nil)
//Below two lines are important for custom transitions.
transitioningDelegate = self
modalPresentationStyle = .Custom
}
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
//Other code for your dialog controller
// .
// .
// .
}
extension AlertDialogViewController : UIViewControllerAnimatedTransitioning {
func transitionDuration(transitionContext: UIViewControllerContextTransitioning) -> NSTimeInterval {
return 0.5 //Add your own duration here
}
func animateTransition(transitionContext: UIViewControllerContextTransitioning) {
//Add presentation and dismiss animation transition here.
}
}
extension AlertDialogViewController : UIViewControllerTransitioningDelegate {
func animationControllerForPresentedController(presented: UIViewController, presentingController presenting: UIViewController, sourceController source: UIViewController) -> UIViewControllerAnimatedTransitioning? {
return self
}
func animationControllerForDismissedController(dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? {
return self
}
}
extension UIViewController {
func showAleartWithMessage(message: String) {
var ad = AlertDialogViewController()
ad.message = message
presentViewController(ad, animated: true, completion: nil)
}
}
You can't
The UIAlertController class is intended to be used as-is and does not
support subclassing. The view hierarchy for this class is private and
must not be modified.
Edit: Relevant code to what I said in comment is added
Imagine you want a dialog box with a UILable and two UIButtons instance
class CustomView : UIView {
var commentLabel: UILable!
var okayButton: UIButton!
var cancelButton: UIButton!
init(frame: CGRect) {
super.init(frame: frame)
commentLabel = UILabel()
okayButton = UIButton.buttonWithType(.Custom)
cancelButton = UIButton.buttonWithType(.Custom)
// TODO: Configuration such target, action, titleLable, etc. left to the user
[commentLabel, okayButton, cancelButton].map { self.addSubview($0) }
}
#IBAction func okayButtonAction(sender: AnyObject) {
// TODO: Complete implementation
}
#IBAction func okayButtonAction(sender: AnyObject) {
// TODO: Complete implementation
}
}
class CustomAlertDialogViewCongroller : UIViewController {
override func loadView() {
self.view = CustomView(frame: CGRect(x: 0, y: 0, width: 200, height: 100))
}
}
// In the view controller that you want to present that alert dialog. Let's call it viewController
let customAlertDialogViewController = CustomAlertDialogViewCongroller()
customAlertDialogViewController.modalPresentationStyle = .UIModalPresentationFormSheet
customAlertDialogViewController.modalTransitionStyle = .CoverVertical
viewController.presentViewController(customAlertDialogViewController, animated: true, completion: nil)

Resources