Dynamic Creation UIButton AddTarget on UIView executed on UIViewController - ios

Currently this code that executes a Tag Styled List, The issue remains when I want to try to pass the addTarget Action optionClicked to my UIViewController
DrawerView.swift
let menuOptions = ["Info", "Actions", "Users", "Patiens"]
menuOptions.forEach({
let button = UIButton()
button.setTitle($0, for: .normal)
if $0.contains(menuOptions[0]) {
button.style(with: .filled)
} else {
button.style(with: .outlined)
button.addTarget(self, action: #selector(optionClicked(_:)), for: .touchUpInside)
}
actionStackView.addArrangedSubview(button)
})
DrawerController.swift
class DrawerController: UIViewController {
var shareView = DrawerView()
var viewModel: CarDetailViewModel?
override func loadView() {
shareView.viewModel = viewModel
view = shareView
}
#objc func optionClicked(_ sender: UIButton) {
let feedbackGenerator = UISelectionFeedbackGenerator()
feedbackGenerator.selectionChanged()
let optionClicked: String = sender.titleLabel?.text ?? "0"
switch optionClicked {
case "Actions": present(DrawerActionController(), animated: true, completion: nil)
case "Notifications":
let viewController = DrawerNotificationController()
viewController.carDetailViewModel = viewModel
present(viewController, animated: true, completion: nil)
case "Patients":
let viewUserController = DrawerUserController()
viewUserController.carPath = "9000"
present(viewUserController, animated: true, completion: nil)
default: break
}
}
}
Tried button.addTarget(self, action: #selector(optionClicked(_:)), for: .touchUpInside) but did not work out.

In the method button.addTarget(self, action: #selector(optionClicked(_:)), for: .touchUpInside) you need to provide pointer to the viewController which will receive the action.
The easiest way in your case would be to create lazy variable DrawerView with DrawerController on the init and use the drawer controller in the button action.
DrawerView.swift
class DrawerView: UIView {
private unowned let drawerController: DrawerController
init(drawerController: DrawerController) {
self.drawerController = drawerController
}
... wherever is your code placed ...
let menuOptions = ["Info", "Actions", "Users", "Patiens"]
menuOptions.forEach({
let button = UIButton()
button.setTitle($0, for: .normal)
if $0.contains(menuOptions[0]) {
button.style(with: .filled)
} else {
button.style(with: .outlined)
button.addTarget(drawerController, action: #selector(DrawerController.optionClicked(_:)), for: .touchUpInside)
actionStackView.addArrangedSubview(button)
}
})
....
}
It's important you use unowned or weak to prevent retain cycles and memory leaks
To create the DrawerView you can use a lazy variable:
lazy var shareView: DrawerView = DrawerView(drawerController: self)
lazy will allow you to use self, you can also use optional and create the variable later, but that's basically what lazy does.
Hope it helps!

Related

Custom anonymous closure navigation back button inside of UiView()

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.

Adding a target to button inside a closure doesn't work

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)
}

UIButton not performing action from function in a different class

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(_:)))

Button to next view controller

I want to create a Button that presents another view controller when it is tapped.
I am trying to to this via selector but when I click the button I get a Thread1: signal SAGABRT error.
So I commented out the presenting and just put in a print to console-statement. I also get the same error. :(
My code is the following:
override func viewDidLoad() {
var menuView = UIView()
var newPlayButton = UIButton()
var newPlayImage = UIImage(named: "new_game_button_5cs")
var newPlayImageView = UIImageView(image: UIImage(named: "new_game_button_5cs"))
newPlayButton.frame = CGRectMake(0, 0, newPlayImageView.frame.width, newPlayImageView.frame.height)
newPlayButton.setImage(newPlayImage, forState: .Normal)
newPlayButton.addTarget(self, action:"showNewPlay:", forControlEvents: UIControlEvents.TouchUpInside)
menuView.addSubview(newPlayButton)
self.view.addSubview(menuView)
}
func showNewPlay(sender:UIButton!) {
//var vc = self.storyboard?.instantiateViewControllerWithIdentifier("newGameID") as ViewController
//self.presentViewController(vc, animated: false, completion: nil)
println("Button tapped")
}

Passing Data through View Controllers with Swift without Using Storyboard

I am trying to create a transition between two View Controllers (the second presented modally) that passes data parsed from an API. A button is created based on this data (it affects what the button says). This is within the closure of an API call, and I store the returned data into a variable within the class (i.e. self.data = returnedData)
let button = UIButton.buttonWithType(UIButtonType.System) as UIButton
button.frame = CGRectMake(0, 100, UIScreen.mainScreen().bounds.width, 50)
button.backgroundColor = UIColor.whiteColor()
button.setTitle("\(buttonTitleInfo)", forState: UIControlState.Normal)
button.addTarget(self, action: "buttonAction:", forControlEvents: UIControlEvents.TouchUpInside)
self.view.addSubview(button)
Then:
func buttonAction(sender:UIButton!)
{
// the data is usable here but I don't know how to pass it via this button
let viewController:UberWebviewController = UberWebviewController()
self.navigationController?.presentViewController(viewController, animated: true, completion: nil)
}
I am not using Storyboard or IB and most of the tutorials/solutions I have found use things specific to those.
How can I pass the returned data without using those?
Make a var on UberWebviewController of the type you want, for example [Any]?:
var dataFromAPI : [Any]?
Then set it after creating the controller but before presenting it:
let viewController = UberWebviewController()
viewController.dataFromAPI = whateverYourDataIs
self.navigationController?.presentViewController(viewController, animated: true, completion: nil)
Then within UberWebviewController, unwrap the optional:
func doSomething {
if let myData = dataFromAPI {
// do something with myData
} else {
// no data was obtained
}
}
Alternatively, you could make dataFromAPI non-optional and pass the data in the initializer:
class UberWebviewController : UIViewController {
var dataFromAPI : [Any]
init(data someData : [Any]) {
self.dataFromAPI = someData
super.init()
}
required init(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
Then just pass it during initialization:
let viewController = UberWebviewController(data: whateverYourDataIs)
self.navigationController?.presentViewController(viewController, animated: true, completion: nil)

Resources