passing multiple closures as parameters to a function in swift - swift

So I am trying to pass two closures to a function which creates a subview. The main part of function that takes closures as arguments and calls them is as follows:
///goButton and cancelButton are class level variables
var goButton = UIButton(type: .system)
var cancelButton = UIButton(type: .system)
func addSubViewWithAction(_ titleString:String, _ button1Text:String, _ button2Text:String, closureYes:#escaping ()->(), closureNo:#escaping ()->()) {
goButton.actionHandle(controlEvents: UIControlEvents.touchUpInside,
ForAction:closureYes)
cancelButton.actionHandle(controlEvents: UIControlEvents.touchUpInside,
ForAction:closureNo)
}
here is how I am trying to call it.
addSubViewWithAction("Hide Penguin here?","Yes","Cancel", closureYes: switchPlayers, closureNo: deletePenquin)
The problem is that it calls the deletePenguin function for both the buttons and never calls the switchPlayers function.
here is how I am adding buttons to main view through subview
//v here is a UIView object
//Add all buttons and text to subView
v.addSubview(titleField)
v.addSubview(goButton)
v.addSubview(cancelButton)
v.layer.cornerRadius = 8
//Add subView to main view
window.addSubview(v)

The problem is that actionHandle somehow works statically, so it will overwrite any previous assignment with the most recent one.
You could do the following (no complete code solution here, only pseudo-code):
Subclass UIButton
Add an instance variable that holds the closure to be executed
Add an instance (helper) func that acts as the target for your event and inside executes the closure above
Create a func that takes the closure to be excecuted as a parameter. Inside,
Assign your instance variable with the provided closure
Call addTarget(_:action:for:) with your helper func as the target
If you want to support different UIControlEvents, you'll have to improve those steps a little, maybe by using a dictionary that maps the event to the closure or so.

Related

How can I define the touchUpInside action for KCFloatingActionButton?

I want to define action for my KCFloatingActionButton. UITapGestureRecognizer is defined for the fab button in ViewController, but I want to do this action in manager class or on another page. I got the KCFloatingActionButton view, but I can't give it a click action.When I click on KCFloatingActionButton I don't want to define a new UITapGestureRecognizer with addGestureRecognizer ,I want it to take the action it is defined.
I can do this for UIButton or UIBarButtonItem as follows
button.sendActions(for: .touchUpInside)
barButtonItem.target?.perform(barButtonItem.action, with: nil)
How can I do this action for KCFloatingActionButton ?
You can try something like this:
button.addTarget(self, action: #selector(handleButtonAction), for: .touchUpInside)
#objc func handleButtonAction(){
//Put your logic here
}
The self in addTarget refers where will be the action to be executed, in this case will be in the current ViewController.
The #selector(handleButtonAction) is the function that will be executed when the user touch the button, in this case will be an Objective-C function (that's why is use the "#selector").
The .touchUpInside is the event that has to be triggered to execute the function.
And finally the function itself #objc func handleButtonAction() { } will execute all the actions that you define for the touch up event.
the #objc attribute comes in: when you apply it to a class or method it instructs Swift to make those things available to Objective-C as well as Swift code.

How to access an object inside a ViewController from within a TableViewCell

I am fairly new at coding in swift and I've been trying to find a solution to my problem for the past couple days, and am not able to.
I have a class named userData with various properties:
class UserData {
var name: String
var age: Int
var credits: Int = 1
...
init(name: String, age: Int) {
self.name = name
self.age = age
}
}
Inside the viewController.swift I have declared an object of this class:
...
var user = UserData(name: "testing", age: 5)
...
Inside the same viewController I have a UITableView with a few UITableViewCells. In every cell there is a UIStepper
Using the UIStepper from a cell I want to increment or decrement the property credits of my object user that sits inside the viewController.swift, and do that from within the tableViewCell.swift file
From what I can find, I think I should use a delegate but I can figure out how to implement it.
More information:
Pardon the art work, I am not an artist...
The user has a set amount of ants available to work (this number is a property of my object user). There is also a property for the amount of ants currently working ('ants used' in my screen shot).
At first, the white label on top says '0/5' (Meaning the user has 5 available ants to work but none are currently working).
When the user increments the stepper for 'scavenger ants', the white label on top should say '1/5' (Meaning that there is currently 1 ant working out of 5 that are available).
What I want to do, is that when the user clicks on a stepper, the user's property for the 'ants currently working' increments or decrements appropriately.
Thank you
Set that logic up in the view controllers' cellForRowAt.
When setting up the cell, you can add a function inside your view controller as the target every-time the UIStepper value changes:
// inside cellForRowAtIndexPath:
cell.addTarget(self, action: #selector(doSomething(sender:), for: .valueChanged)
Inside doSomething you set up the logic for updating your model.
func doSomething(sender: UIStepper) {
// do stuff here
}
Edit with an example of the delegate pattern which would be a better solution, for future readers.
First create a protocol:
protocol StepperCellDelegate {
func didChangeValueForStepper(inCell: Cell, whateverInfoYouWantHere:...)
func otherUsefulFunctions(...)
}
In your cell class, set a target/action for when your value is changed:
// inside the cell's initialization (`init(style:reuseIdentifier:)`
self.addTarget(self, action: #selector(valueChanged(sender:), for: .valueChanged)
Your cell also needs a delegate property:
weak var stepperCellDelegate: StepperCellDelegate?
doSomething would look something like this:
func valueChanged(sender: UIStepper) {
stepperCellDelegate?.didChangeValueForStepper(inCell: self, ...)
}
Your ViewController will implement the protocol
extension MyViewController: StepperCellDelegate {
func didChangeValueForStepper(inCell: Cell, whateverInfoYouWantHere:...) {
// implementation here
}
func otherUsefulFunctions(...){
// implementation here
}
}
And inside cellForRowAtIndexPath set itself as the delegate:
// inside cellForRowAtIndexPath:
cell.stepperCellDelegate = self
Delegation is the proper way of handling this.
That UItableviewCell — both appear to be similar —
should expose a protocol with a set of methods
that the UIViewController can implement. Methods are
triggered when you tap on the UIStepper widget.
Best to stick to this methodology, and reuse it everywhere. Makes
things manageable loosely coupled.

Different behavior between addTarget and addGestureRecognizer

I have a function that creates a button with a selector function as a target. The address of a button gets passed to handleSelectPhoto.
lazy var image1Button = createButton(selector: #selector(handleSelectPhoto))
func createButton(selector: Selector) -> UIButton {
let button = UIButton(type: .system)
button.addTarget(self, action: selector, for: .touchUpInside)
return button
}
#objc func handleSelectPhoto(button: UIButton) {
// Do something with button, this works
}
Now, I am trying to change the class of the above from UIButton to UIImageView like the following,
lazy var image1Button = createButton(selector: #selector(handleSelectPhoto))
func createButton(selector: Selector) -> UIImageView {
let view = UIImageView()
view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: selector))
view.isUserInteractionEnabled = true
return view
}
#objc func handleSelectPhoto(button: UIImageView) {
// HERE, button does not get passed
}
With the above changes, in handleSelectPhoto, button instance is not correct. I can not read it as UIImageView type.
If I add a selector function using addGestureRecognizer, does it behave differently than adding a selector function using addTarget, in terms of how selector function is executed with parameters? Maybe I am not understanding how this selector function works...
Adding a target to something like UIGestureRecognizer or UIButton only passes one parameter to the selected function. This parameter depends on the type you are about to add the target on.
In your case the first code snippet works because you are adding a target to an UIButton, so your selected function gets passed this UIButton instance.
In your second scenario you add the target to an UITapGestureRecognizer, so the passed instance will be exactly this gesture recognizer, which cannot be of type UIImageView.
So the difference from the target parameter perspective between UIGestureRecognizer and UIButton is no difference. They both pass their instances to the selected function.
From the UIView subclass perspective there is the difference that UIGestureRecognizer is not a subclass of UIView, but UIButton is. That's why you can just use the passed UIButton instance in your first snippet. In the second snippet you need use the view property of UIGestureRecognizer.
guard let imageView = gestureRecognizer.view as? UIImageView else { return }
Besides your actual question it seems important to clarify how to write #selectors correctly. You're doing it correct already. No change necessary. Some may say you need to add (_:) or : to your selector like so: #selector(handleSelectPhoto(_:)) but this isn't true. In general, you only need to add these special characters when you are selecting a method which has an overload method with a different amount of parameters, but the same base name.
You should make your tell while setting the selection that your function will accept a parameter by adding : at the end of method name.
lazy var image1Button = createButton(selector: #selector(handleSelectPhoto:))
UIKit will automatically understand that the selector methods parameter will be of type UITapGestureRecognizer. Now rewrite the below method like this and you will be good to go.
#objc func handleSelectPhoto(gesture: UITapGestureRecognizer) {
if let buttonImageView = gesture.view as? UIImageView {
//Here you can make changes in imageview what ever you want.
}
}

"classname has no member functionname" when adding UIButton target

Super newb in Swift and iOS development here.
I am following this tutorial about implementing a custom control in a single view iOS app. It's a Swift 2 tutorial, but so far I'm doing OK transposing everything to 3 as I go (I use XCode 8 Beta).
I have a custom class, RatingControl, connected to a View in the storyboard.
In the class's constructor, I create a button:
let button = UIButton(frame: CGRect(x: 0, y: 0, width: 44, height: 44))
button.backgroundColor = UIColor.red()
Then, I try to assign an action to the button. The tutorial says I should do it like so:
button.addTarget(self, action: #selector(RatingControl.ratingButtonTapped(_:)),
for: .touchDown)
and then create, in the same RatingControl class, the method:
func ratingButtonTapped(button: UIButton) {
print("Button pressed 👍")
}
But when I compile, it complains:
type "RatingControl" has no member "ratingButtonTapped"
I've made 100% sure the function is there, in the class, and properly named. Full source
Is there something obvious I'm missing?
What I tried:
Added #objc to the class definition as per this answer (but that seems weird for a Swift-only thing, no?)
Made ratingButtonTapped() explicitly public (but that doesn't look like it should be necessary)
Fiddled around with strings instead of selectors, button.addTarget(self, action: "RatingControl.ratingButtonTapped", for: .touchDown) and many more, but that just crashes it later.
In Swift 3, method reference for: func ratingButtonTapped(button: UIButton) becomes ratingButtonTapped(button:).
So, using #selector(RatingControl.ratingButtonTapped(button:)) also work.
And if you want to keep #selector(RatingControl.ratingButtonTapped(_:)), then you need to declare the ratingButtonTapped method as:
func ratingButtonTapped(_ button: UIButton) { //<- `_`
print("Button pressed 👍")
}
And if you have only one ratingButtonTapped method in the class, you can address the selector as #selector(RatingControl.ratingButtonTapped) or simply (from inside RatingControl class) #selector(ratingButtonTapped).
This happened because Swift 3 has changed the way it handles the first parameter name. In Swift 3, all parameter names must be used when calling a function unless an explicit _ was declared as the parameter name.
What you used as your selector was fine if you had declared your function as:
func ratingButtonTapped(_ button: AnyObject) {
print("Button pressed 👍")
}
You could also have used this as your selector:
#selector(RatingControl.ratingButtonTapped(button:))
Added #objc to the class definition as per this answer (but that seems
weird for a Swift-only thing, no?)
Your code may be in Swift, but you are interacting with the Objective-C runtime when you are coding for Cocoa Touch (iOS framework). The selector is a function that needs to be visible to the Objective-C runtime. You get this for free most of the time because you are implementing this in a class that ultimately inherits from NSObject (like UIViewController). If you have a Swift only class that doesn't inherit from NSObject, then you can add #objc to make the class and methods visible to the Objective-C runtime.
If you want to call an action that is in your View Controller from a Different Class you can try this.
Use ViewController() for your target. Use ViewController.functionName for your selector. Do not use a helper method for the view controller variable like "vc", otherwise you will not be able to access objects within the ViewController.
Here is an example target:
self.addTarget(ViewController(), action:#selector(ViewController.Test(_:)), for: UIControlEvents.touchDragInside)
In your View Controller, here is an example Action
#IBAction func Test(_ sender: Any?) {
print("Goodtime was here")
}
In the target you must add () but not in the action's selector. You do not have to call #IBAction, it can just be func. Some people use #objc or public any of those prefixes on the action should work.
Review, if the action is in a different Class or ViewController, you must put the the Class reference in both the target and the action's selector. Otherwise, it will try to always call the action within the same file regardless if it is correct in the Selector. Likewise, if the action is in the same file use, self for the target and inside the action's selector.
Cheers
When adding button targets in swift 3 first you need to be in class.
class SomeClass {
// ...
let button = UIButton()
// configure button to taste...
button.addTarget(self,
action: #selector(doSomething),
for: .touchUpInside)
// ...
#objc public func doSomething() {
print("hello, world!")
}
// ...
}

why pass viewController as variable?

First of all I have to say I am really new to swift and Objective C.I am learning them by myself.
I have a question for this code
I have a delegate in my SettingViewController called "settingsViewControllerFinished" and it pass the whole controller as a variable.
the code like this:
in my SettingViewController.swift
protocol SettingViewControllerDelegate: class {
func settingsViewControllerFinished(settingsViewController: SettingsViewController)
}
#IBAction func close(sender: AnyObject) {
dismissViewControllerAnimated(true, completion: nil)
self.delegate?.settingsViewControllerFinished(self)
}
I am confused.What did you mean if you pass the whole controller as a variable?(maybe the question is silly for you)
in my viewController:
func settingsViewControllerFinished(settingsViewController: SettingsViewController)
{
self.brushWidth = settingsViewController.brush
self.opacity = settingsViewController.opacity
self.red = settingsViewController.red
self.green = settingsViewController.green
self.blue = settingsViewController.blue
}
I guess the reason is:I pass everything in SettingViewController to ViewController so that I could use the variables in SettingViewController.
Am I rihgt?
Generally you are correct, yes: passing the SettingViewController back to its delegate enables the original caller to not have to keep a reference to the created and shown SettingViewController since the delegate method sends the relevant information along already.
But there is more: In some cases of delegates this style is useful for something different. Imagine a click handler consisting of a function func somethingGotClicked(sender:YourSenderType). If your class creates multiple instances of YourSenderType and shows them at the same time registering itself as their delegate there would be no way to know which one got clicked if there was no sender parameter. In some func somethingGotClicked() you would not know which one got clicked. That capability is often needed when showing multiple UITableView or UICollectionView is one single view with one single instances set as their delegate.