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.
Related
I try to setup toolbar programmatically, but nothing of this works:
In AppDelegate
UIApplication.shared.delegate?.window??.rootViewController?.navigationController?.toolbar.isTranslucent = false
UIApplication.shared.delegate?.window??.rootViewController?.navigationController?.toolbar.tintColor = .black
In ViewDidLoad
navigationController?.toolbar.isTranslucent = false
navigationController?.toolbar.tintColor = .black
Why?
The second thing is when I navigate to another view controller, my black toolbar is shown for a moment (I hide it with navigationController?.setToolbarHidden(true, animated: true)). How can I completely hide it on transitions?
You could try subclassing UIViewController as in Apple's UIKitCatalog sample application. It uses the storyboard, but that might work for your project.
class CustomToolbarViewController: UIViewController {
#IBOutlet var toolbar: UIToolbar!
override func viewDidLoad() {
super.viewDidLoad()
let toolbarButtonItems = [
customImageBarButtonItem
]
toolbar.setItems(toolbarButtonItems, animated: true)
}
// MARK: - UIBarButtonItem Creation and Configuration
var customImageBarButtonItem: UIBarButtonItem {
// item set up code
}
I began to learn Swift recently. When I tried to make my first App I got confused with UIBarButtonItem. If I put let UIBarButtonItem initialization outside the viewDidLoad() function, nothing happens when I press the Next Button.
class ViewController: UIViewController, UITextFieldDelegate {
let rightBarButton: UIBarButtonItem = UIBarButtonItem(title: "Next", style: .plain, target: self, action: #selector(onClickNext(button:)))
override func viewDidLoad() {
super.viewDidLoad()
self.view.backgroundColor = .white
self.navigationItem.rightBarButtonItem = rightBarButton
}
func onClickNext(button: UIBarButtonItem) {
print("should push view controller")
}
}
However, when I put the initialization into the viewDidLoad() function, the output area does output the sentense that I set in the onClickNext(button:) function.
class ViewController: UIViewController, UITextFieldDelegate {
var rightBarButton: UIBarButtonItem?
override func viewDidLoad() {
super.viewDidLoad()
self.view.backgroundColor = .white
self.rightBarButton = UIBarButtonItem(title: "Next", style: .plain, target: self, action: #selector(onClickNext(button:)))
self.navigationItem.rightBarButtonItem = rightBarButton
}
func onClickNext(button: UIBarButtonItem) {
print("should push view controller")
}
}
Also, I I found that when I put the initialization outside the viewDidLoad() function, and I add a UITextField to viewController, the rightBarButton works if I touch the textfield before I press the button.
That make me confused. What is the mechanism?
Well, maybe you are missing how a ViewController works inside.
First, viewDidLoad is the area were you usually setup or initialize any view or properties of the view. This method is also called only once during the life of the view controller object. This means that self already exists.
Knowing this, is important to understand what a let property does, (from Apple)
A constant declaration defines an immutable binding between the constant name and the value of the initializer expression; after the value of a constant is set, it cannot be changed. That said, if a constant is initialized with a class object, the object itself can change, but the binding between the constant name and the object it refers to can’t.
Even though the upper area is where you declare variables and constants, is usually meant for simple initialization, it's an area for just telling the VC that there is an object that you want to work with and will have a class global scope, but the rest of functionality will be added when the view hierarchy gets loaded (means that the object does not depends of self, for example, when adding target to a button, you are referring to a method inside of self)....this variables or constants are called Stored Properties
In its simplest form, a stored property is a constant or variable that is stored as part of an instance of a particular class or structure. Stored properties can be either variable stored properties (introduced by the var keyword) or constant stored properties (introduced by the let keyword).
And finally, you have a lazy stored property that maybe can be applied for what you want:
A lazy stored property is a property whose initial value is not calculated until the first time it is used. You indicate a lazy stored property by writing the lazy modifier before its declaration.
Solution: create a lazy var stored property or add his properties inside ViewDidLoad (when self already exists)
lazy private var doneButtonItem : UIBarButtonItem = {
[unowned self] in
return UIBarButtonItem(title: "Next", style:UIBarButtonItemStyle.Plain, target: self, action: #selector(onClickNext(button:)))
}()
OR
let rightBarButton: UIBarButtonItem?
override func viewDidLoad() {
super.viewDidLoad()
rightBarButton = UIBarButtonItem(title: "Next", style: .plain, target: self, action: #selector(onClickNext(button:)))
}
import UIKit
class ViewController: UIViewController {
var btnName = UIButton()
override func viewDidLoad() {
super.viewDidLoad()
btnName.setImage(UIImage(named: "imagename"), for: .normal)
btnName.frame = CGRect(x:0,y: 0,width: 30,height: 30)
btnName.addTarget(self, action: #selector(addTargetActionForButton(:_)), for: .touchUpInside)
let rightBarButton = UIBarButtonItem(customView: btnName)
self.navigationItem.rightBarButtonItem = rightBarButton
}
func addTargetActionForButton(_ sender : UIButton){
}
}
Your are taking let variable out side of viewDidLoad You can not take let global variable. if you want to take let than you need to declare inside viewDidLoad. Fore more info let and var check this link What is the difference between `let` and `var` in swift?
override func viewDidLoad() {
let rightBarButton: UIBarButtonItem = UIBarButtonItem(title: "Next", style: .plain, target: self, action: #selector(onClickNext(button:)))
super.viewDidLoad()
self.view.backgroundColor = .white
self.navigationItem.rightBarButtonItem = rightBarButton
}
UIBatButtonItem may contain UIButton
above example image have UINavigationItem which contains LeftBarButtonItems ([UIBarButton]), and RightBarButtonItems([UIBarButtonItem]), each UIBarButtonItem contain UIButton
we can customise the UIButton to specify how to display in View
And we can connect button action directly to the UIButton
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.
I need to change color of UIView then I tap on it. How I can do this?
P.S. Sorry for my english
here are the steps.
Drag a UIView to your ViewController
Then create an IBOutlet to it. (just drag a connection to your viewcontroller class).
then create gesture for it.
then create an action for it.
assign the action
here is the full code for your view controller
import UIKit
class ViewController: UIViewController {
//Outlet Connection to your View
#IBOutlet weak var myView: UIView!
override func viewDidLoad() {
super.viewDidLoad()
myView.layer.borderColor = UIColor.black.cgColor
myView.layer.borderWidth = 2
let taptoColorGesture = UITapGestureRecognizer.init(target: self, action: #selector(ViewController.changeMyColor))
myView.addGestureRecognizer(taptoColorGesture)
// Do any additional setup after loading the view, typically from a nib.
}
func changeMyColor () {
let colorR : Float = Float(Int(arc4random_uniform(255)))
let colorG : Float = Float(arc4random_uniform(255))
let colorB : Float = Float(arc4random_uniform(255))
let themyColor = UIColor.init(colorLiteralRed: colorR/255, green: colorG/255, blue: colorB/255, alpha: 1.0)
myView.backgroundColor = themyColor
//this function creates random color each time
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
and here is the project url to github Change the UIView Color on tap in Swift
// first go to identity inspector and set 'user interaction enabled' on the view
// Then put this in the viewDidLoad method (change :myExampleView to your view name)
let tapChangeColor = UITapGestureRecognizer(target: self, action: #selector(changeColor))
self.myExampleView.addGestureRecognizer(tapChangeColor)
// put this method somewhere in ViewController class:
func changeColor() {
self.myExampleView.backgroundColor=UIColor.blue
}
I did some research but I could not find an appropriate answer regarding the following question.
I don't use the interface builder to generate my views so every control is defined programmatically like so :
let loginButton: UIButton = {
let button = UIBUtton()
button.setTitle("Title", forState: .Normal)
// adding some properties to my button
return button
}()
Then I add it to the view of my ViewController like that :
self.view.addSubview(loginButton)
And I add some constraints with the Visual Format Language
For example :
self.view.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat
_("V:|[v0(25)]|", options:NSLayoutFormatOptions(), metrics: nil,
_views: ["v0":loginButton]))
It works really well and I prefer it that way(over the IB) but the main issue is that I end up with really big ViewController classes as I define every controls in the ViewControllers classes.
I guess I should have all the controls declarations, constraints,... in a separate file but I can't find how to do that.
I tried to create a separate class, for example LoginView, which contains all the controls, and a function setupViews(superView: UIView) that adds subviews to the superView parameter and specify constraints. Then in the viewDidLoad() method of my ViewController, I instantiate this class (LoginView) and execute the setupView(superView: self.view). It looks like this :
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let loginView = LoginView()
loginView.setupViews(self.view)
}
}
I am pretty sure this is not the way to go. It also causes problems when I try to addTarget to some of my controls.
My question, what is the best way to do it, following the MVC pattern.
Thank you very much !
EDIT 1 :
As asked in the comments, here is the implementation of the LoginView class (I only kept few controls/constraints to keep it short but it is the same process for every other one)
import UIKit
class LoginView {
let superView: UIView?
init(tempSuperView: UIView){
superView = tempSuperView
}
let usernameTextField: UITextField = {
let textField = UITextField()
textField.translatesAutoresizingMaskIntoConstraints = false
textField.textColor = UIColor.whiteColor()
return textField
}()
let loginButton: UIButton = {
let button = UIButton()
button.translatesAutoresizingMaskIntoConstraints = false
button.setTitle("Login", forState: .Normal)
return button
}()
//some more controls definition
func setupViews() -> Void {
superView!.addSubview(usernameTextField)
superView!.addSubview(loginButton)
superView.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat
_("V:|-150-[v0(50)]", options:NSLayoutFormatOptions(), metrics: nil,
_views: ["v0":usernameTextField]))
superView.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat
_("H:|-150-[v0(25)]", options:NSLayoutFormatOptions(), metrics: nil,
_views: ["v0":usernameTextField]))
//same for the button
}
}
Here is my ViewController :
import UIKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let loginView = LoginView(tempSuperView: self.view)
loginView.setupViews()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}