When i want to add a UIButton in a view controller's view, here are the ways:
First
let button: UIButton = UIButton()
then configure properties in viewDidLoad method.
Second
lazy var button: UIButton = {
let buttonTemp = UIButton()
buttonTemp.backgroundColor = UIColor.clearColor()
button.addTarget(self, action: "connect", forControlEvents: UIControlEvents.TouchUpInside)
self.view.addSubview(buttonTemp)
return buttonTemp
}()
Third
let button: UIButton = {
let button = UIButton(type: .Custom)
button.backgroundColor = UIColor.greenColor()
return button
}()
My question is which way should i use or which way is better?
I dislike the first way because i have to add an extra method to configure other properties.
Second is ok for me, i just need call button anywhere i want to.
I think use let is suit the best so i use the third way, but the thing is i can't call self, which is if i add this link in the closure:
button.addTarget(self, action: "connect", forControlEvents: UIControlEvents.TouchUpInside)
I got the error:
ViewController.swift:24:26: Cannot convert value of type 'NSObject -> () -> ViewController' to expected argument type 'AnyObject?'
So i have add this line(any line with self) out of this closure. Any way can solve this?
Summary, which way is better or suit? Or any better way? thanks!
EDIT:
When i am using Objective C, i'd like use getter in this way
- (UIButton *) button {
if (!_button) {
_button = [[UIButton alloc] init];
_button.backgroundColor = [UIColor redColor];
...
}
return _button;
}
so my viewDidLoad will be clean and looks good:
- (void) viewDidLoad {
...
[self.view addSubview:self.button];
...
}
Styles obviously vary, but where I work we've standardized on the following approach:
class ViewController: UIViewController {
private var button: UIButton!
override func viewDidLoad() {
super.viewDidLoad()
button = {
let button = UIButton(type: .Custom)
button.addTarget(self, action: "buttonTouched:", forControlEvents: .TouchUpInside)
button.otherProperty = ...
return button
}()
view.addSubview(button)
// add constraints for button
}
func buttonTouched(sender: UIButton) {
print("boop")
}
}
The problem with all of your approaches is that:
They're very verbose and not contained in a function, and
You don't have access to self as you've seen
With the approach above (using a force-unwrapped optional), you get the benefit of deferred initialization (i.e. everything happens in viewDidLoad()), you know because you own the object that button will never be nil (thus you don't have to use conditional binding all over the place), and you get contained initialization for all of your UIView properties in one place.
You can obviously (and we do) make your viewDidLoad() function look like this:
override func viewDidLoad() {
super.viewDidLoad()
createViews()
addSubviews()
addViewConstraints()
}
Then you get even better specialization in your functions and your code stays organized.
Related
I have a question, how is it possible to implement the creation of a custom back navigation button inside an UIView(). I have a main controller which contains a collectionView, clicking on any cell goes to a second controller which contains a tableView. I created a separate custom view inside the tableView headers where I added labels, pictures, buttons. I need when clicking a backButton inside a custom view, it will go to the main controller. How can be implemented? I making app only programmatically - (No Storyboard)
CustomView.swift
lazy var backButton: UIButton = {
let button = UIButton(type: .system)
let image = UIImage(systemName: "chevron.left")
button.setImage(image, for: UIControl.State())
button.tintColor = .white
button.isHidden = true
button.addTarget(self, action: #selector(goToBack), for: .touchUpInside)
button.translatesAutoresizingMaskIntoConstraints = false
return button
}()
#objc func goToBack() {
}
First add a callback function in the CustomView. Then call this callback closure from goToBack() method.
class CustomView: UIView {
var backButtonTapped: (() -> Void)?
lazy var backButton: UIButton = {
let button = UIButton(type: .system)
let image = UIImage(systemName: "chevron.left")
button.setImage(image, for: UIControl.State())
button.tintColor = .white
button.isHidden = true
button.addTarget(self, action: #selector(goToBack), for: .touchUpInside)
button.translatesAutoresizingMaskIntoConstraints = false
return button
}()
#objc func goToBack() {
backButtonTapped?()
}
}
In UIViewController where you initialise this CustomView, give the action of the closure.
let view = CustomView()
view.backButtonTapped = { [weak self] in
self?.navigationController?.popViewController(animated: true)
}
You will need to create a delegate for this. In your CustomView make a property weak var delegate: ButtonDelegate
protocol ButtonDelegate: class {
func onTap()
}
And your ViewController holding the CustomView has do implement that protocol and do navigationController.popViewController() in the implemented onTap() method.
Call delegate?.onTap() in your CustomView goToBack() method.
The following code is located inside a subclass of UIView
I am setting up a cancelButton inside a closure:
private var cancelButtonClosure: UIButton = {
...
button.addTarget(self, action: #selector(cancel(_:)), for: .touchUpInside)
...
}()
And at first I instantiated the button inside a function like so:
func showConfirmationView(...) {
...
let cancelButton = self.cancelButtonClosure
...
addSubview(cancelButton)
...
}
However this resulted in the cancel function not being called at all (even though the layout was right and the button was highlighting)
So I made these change:
Removed the addTarget part from the cancelButtonClosure
Added the addTarget part inside the showConfirmationView function
So it looked like that:
func showConfirmationView(...) {
...
let cancelButton = self.cancelButtonClosure
cancelButton.addTarget(self, action: #selector(cancel(_:)), for: .touchUpInside)
...
addSubview(cancelButton)
...
}
It worked: the cancel function was called; but I don't know why. I'm really curious to know why what I did before did not work. Thanks for your insights!
Check your implementation because a setup like this works as expected:
private var cancelButton: UIButton = {
let btn = UIButton(type: .system)
btn.setTitle("Cancel", for: .normal)
btn.addTarget(self, action: #selector(cancelSomething(_:)), for: .touchUpInside)
return btn
}()
#objc func cancelSomething(_ sender: UIButton) {
print("Something has to be cancelled")
}
override func viewDidLoad() {
super.viewDidLoad()
showConfirmationView()
}
func showConfirmationView() {
cancelButton.sizeToFit()
cancelButton.center = view.center
view.addSubview(cancelButton)
}
I have a class where written is a function creating my button:
LoginButton.swift
func createButton() {
let myButton: UIButton = {
let button = UIButton()
button.addTarget(self, action: #selector(Foo().buttonPressed(_:)), for: .touchUpInside)
}()
}
Now in my second class, Foo.swift, I have a function that just prints a statement
Foo.swift
#objc func buttonPressed(_ sender: UIButton) {
print("button was pressed")
}
When ran I get no errors except when I try to press the button, nothing happens. Nothing prints, the UIButton doesn't react in any way. Really not sure where the error occurs because Xcode isn't printing out any type of error or warning message.
The action method is called in the target object. Thus, you have either to move buttonPressed to the class which contains createButton or to pass an instance of Foo as a target object.
But note that a button is not the owner of its targets. So, if you just write:
button.addTarget(Foo(), action: #selector(buttonPressed(_:)), for: .touchUpInside)
This will not work, because the Foo object is immediately released after that line. You must have a strong reference (e.g. a property) to Foo() like
let foo = Foo()
func createButton() {
let myButton: UIButton = {
let button = UIButton()
button.addTarget(foo, action: #selector(buttonPressed(_:)), for: .touchUpInside)
}()
}
You are missing with target. So make instant of target globally and make use of it as target for button action handler.
class ViewController: UIViewController {
let foo = Foo()
override func viewDidLoad() {
super.viewDidLoad()
createButton()
}
func createButton() {
let myButton: UIButton = {
let button = UIButton(frame: CGRect(x: 0, y: 0, width: 100, height: 50))
button.backgroundColor = UIColor.red
button.setTitle("Tap me", for: .normal)
button.addTarget(self.foo, action: #selector(self.foo.buttonPressed(_:)), for: .touchUpInside)
return button
}()
myButton.center = self.view.center
self.view.addSubview(myButton)
}
}
Class Foo:
class Foo {
#objc func buttonPressed(_ sender: UIButton) {
print("button was pressed")
}
}
Just pass Selector as function argument.
func createButtonWith(selector: Selector) {
let myButton: UIButton = {
let button = UIButton()
button.addTarget(self, action: selector), for: .touchUpInside)
}()
}
And call this function like below...
createButtonWith(selector: #selector(Foo().buttonPressed(_:)))
I'm trying to learn how to create button target actions, however, when I press the button, I get those LLDB errors and I get told that it was an 'unrecognized selector sent to class'.
Where am I going wrong here?
StatusCell.swift:
let phoneIcon: UIButton = {
let iv = UIImageView()
iv.translatesAutoresizingMaskIntoConstraints = false
iv.image = UIImage(named: "Phone3")?.imageWithRenderingMode(.AlwaysTemplate)
let phoneBtn = UIButton(type: .Custom)
phoneBtn.addTarget(CallButton.self, action: #selector(CallButton.buttonPressed(_:)), forControlEvents: .TouchDown)
phoneBtn.addTarget(CallButton.self, action: #selector(CallButton.buttonReleased(_:)), forControlEvents: .TouchUpInside)
phoneBtn.translatesAutoresizingMaskIntoConstraints = false
phoneBtn.setImage(iv.image!, forState: .Normal)
phoneBtn.tintColor = UIColor(r: 224, g: 224, b: 224)
return phoneBtn
}()
Here's the CallButton class where I call for buttonPressed and buttonReleased.
class CallButton: UIControl {
func buttonPressed(sender: AnyObject?) {
print("Pressed")
}
func buttonReleased(sender: AnyObject?) {
print("Let go")
}
}
The value for parameter target must be an instance of CallButton, not the type itself.
You are setting the class itself, not an instance, as the target of the action.
Therefore, the method you set as the action should be implemented as a class method, not an instance method:
class func buttonPressed(sender: AnyObject?) {
print("Pressed")
}
I have the following code.
import UIKit
class ViewController: UIViewController {
var button : UIButton?
override func viewDidLoad() {
super.viewDidLoad()
button = UIButton.buttonWithType(UIButtonType.System) as UIButton?
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
I get the following error
ERROR:
'AnyObject' is not convertible to 'UIButton?'
I know I might be doing something fundamentally wrong. I would like to know what that is.
According to me:
I have declared button as an Optional UIButton
- Which I think means, that the value of button can be unset or nil
Therefore,
while initialising it the type is mentioned as UIButton?
Is this it right way ?
You can't cast to an optional UIButton in the way you're doing it. The correct way to cast to an optional UIButton is:
button = UIButton.buttonWithType(UIButtonType.System) as? UIButton
Interpret this as: This cast can either return nil or an UIButton object, resulting in an optional UIButton object.
Follow the below code
var myBtn = UIButton.buttonWithType(UIButtonType.System) as UIButton
//OR
var myBtn = UIButton.buttonWithType(UIButtonType.Custom) as UIButton
//OR
var myBtn = UIButton()
myBtn.setTitle("Add Button To View Controller", forState: .Normal)
myBtn.setTitleColor(UIColor.greenColor(), forState: .Normal)
myBtn.frame = CGRectMake(30, 100, 200, 400)
myBtn.addTarget(self, action: "actionPress:", forControlEvents: .TouchUpInside)
self.view.addSubview(myBtn)
//Button Action
func actionPress(sender: UIButton!)
{
NSLog("When click the button, the button is %#", sender.tag)
}
Please try below code:
var button = UIButton(frame: CGRectMake(150, 240, 75, 30))
button.setTitle("Next", forState: UIControlState.Normal)
button.addTarget(self, action: "buttonTapAction:", forControlEvents: UIControlEvents.TouchUpInside)
button.backgroundColor = UIColor.greenColor()
self.view.addSubview(button)
This code should do the job.
button = UIButton.buttonWithType(UIButtonType.System) as! UIButton
In this case the force cast done with the ! is a safe option because the documentation does guarantee that the method returns a UIButton.
You can also create the button during the declaration of the property:
class ViewController: UIViewController {
var button = UIButton.buttonWithType(UIButtonType.System) as! UIButton
...
This way there is no need to declare the property as an optional type.