button action with multiple parameter - ios

I'm working with swift. I've this view controller:
In this picture from 1.1.1 to 1.2.6 all are uibutton. This button r created programatically. Now on each button click I need to pass 2 parameter. If I consider 1.1.1 Overflater , then I need to pass "1.1.1" and "Overflater" as parameter. I've tried like below:-
ov_button.tag = "1.\(i).1"
ov_button.addTarget(self, action: #selector("i3Vatrom.romBeskrivelse:")
, forControlEvents: UIControlEvents.TouchUpInside)
But it's not working. Any idea how to do this....

I think, you should use UITableView hierarchy if you don't used UITableView, use UITableView and use section and row tag properties for managing your buttons. didSelectRowAtIndexPath function using for button clicks.

You cannot pass anything to an action handler function. The action handler function automatically receives the sender (here, the button) as a parameter — and that's all. The trick, therefore, is to build the knowledge of the information you want passed into the button object itself, since this will be what is passed! In your case, it looks like you could just ask for the button's title for the Normal state and parse it into the "1.1.1" and "Overflater".

after trying and thinking so much I found an easy solution of this question.
below code for button add target:-
ov_button.addTarget(self, action: #selector(i3Vatrom.romBeskrivelse), forControlEvents: UIControlEvents.TouchUpInside)
I've used below function:-
func romBeskrivelse(sender:UIButton!) {
let title = sender.titleLabel!.text!
let titleArr = title.characters.split{$0 == " "}.map(String.init)
print(titleArr) // print ["1.2.1", "Overflater"]
}
That's why I thought I must post the answer. may be this will help other.

Related

iOS: Good way to pass value from image button to func in the class

Initially I had two buttons with titles: "+" and "-". Both buttons are located in table cell. I have used protocol and delegate to pass value from button title to function call which depends on input params. If input parameter is "-" - decrease value, if "+" - increase.
But later I removed titles and replaced buttons with respective images. And here I faced an issue - I cannot call function properly because title is blank.
I have implemented the following workaround. I have set Accessibility Identifier for both buttons. For example for +:
And in cell class I used accessibilityIdentifier:
#IBAction func didPressOrderCellButton(_ sender: UIButton) {
cellDelegate?.didPressOrderCellButton(order: order!, menuItem: menuItem!, action: sender.accessibilityIdentifier!)
}
But... I have some doubts if it's proper way to do this. Will it create any issues in future if I decide to work with accessibility feature?
I do not want to use title and add code to ViewController class to hide it every time view is loaded or appeared. I want to avoid such solutions in my code because it's too difficult to support such solutions I believe.
I have tried to find another button identifier that I can use, but didn't succeed.
The simple approach would be just set the tag value inside your button and then check the same tag value and perform your operation Plus or Minus accordingly.
For instance:-
if sender.tag == 0 {
//Do minus
} else if sender.tag == 1 {
//Do plus
}

passing parameters to a Selector in Swift

I am building an app for keeping track of reading assignments for a university course. Each ReadingAssignment has included a Bool value that indicates if the reader has finished reading the assignment. The ReadingAssignments are collected into WeeklyAssignment arrays. I want to have the user be able to touch a label and have a checkmark appear and show the assignment as completed. I would like this touch to also update the .checked property to true so I can persist the data.
So, I am trying to have the gestureRecognizer call the labelTicked() method. This works and prints to the console. However, when I try to pass in the assignment parameter, it compiles, but crashes on the touch with an "unrecognized selector" error. I have read every topic i can find here, and haven't found the solution. They all say ":" signifies a Selector with parameters, but still no go.
Can you see what I am doing wrong?
func configureCheckmark(cell: UITableViewCell, withWeeklyAssignment assignment: WeeklyAssignment) {
let checkLabel = cell.viewWithTag(1002) as! UILabel
checkLabel.userInteractionEnabled = true
let gestureRecognizer = UITapGestureRecognizer(target: self, action: Selector("labelTicked:assignment"))
checkLabel.addGestureRecognizer(gestureRecognizer)
}
#objc func labelTicked(assignment: WeeklyAssignment) {
assignment.toggleCheckmark()
if assignment.checked {
label.text = "✔︎"
} else {
label.text = ""
}
}
I would also love to pass in the UILabel checkLabel so I can update it in the labelTicked() method.
Thanks for your help.
There are two distinct problems here:
The syntax for the selector is wrong; the : doesn't mark the beginning of a parameters part, it merely marks that this function takes parameters at all. So the tap recognizer should be initialized as UITapGestureRecognizer(target: self, action: "labelTicked:"). Easy fix. That is the good news.
The bad news: even with perfect syntax it will not work the way you set it up here. Your tap recognizer cannot pass a WeeklyAssignment object as a parameter. In fact, it cannot pass any custom parameter at all. At least, not like that.
However, you can pass it in the sender (which is usually the view the gesture recognizer is attached to). You can grab it by changing your method to
func labelTicked(sender: AnyObject) {
(note that AnyObject may be declared as a more specific type if you know exactly what to expect.)
Going through the sender, you could now theoretically infer which label it is that has been tapped, which data entity that labels corresponds to, and which state the checked property of that entity is in. I think this would become very convoluted very quickly.
Seemingly straightforward things becoming convoluted is usually a good sign that we should take a step back and look for a better solution.
I'd suggest dropping the whole GestureRecognizer approach at this point, and instead exploit the fact that each cell in a table view already comes with its own "tap recongizing" functionality out of the box: the didSelectRowAtIndexPath: method of the table view's delegate. There, you can easily use the supplied NSIndexPath to retrieve the corresponding model entity from the data source to read and modify its parameters as you see fit. Via cellForRowAtIndexPath: you can then get a reference to the correct cell and change its contents accordingly.
Update for Swift 3:
As the Swift language evolves and using String-based selectors (e.g. "labelTicked:") is now flagged as deprecated, I think it's appropriate to provide a small update to the answer.
Using more modern syntax, you could declare your function like this:
#objc func labelTicked(withSender sender: AnyObject) {
and initialize your gesture recognizer like this, using #selector:
UITapGestureRecognizer(target: self, action: #selector(labelTicked(withSender:)))
The correct selector for that function is labelTicked:. Furthermore, you can use a string directly as a selector. Thus:
let gestureRecognizer = UITapGestureRecognizer(target: self, action: "labelTicked:")
But you can't arrange to pass an arbitrary object along to the method when the recognizer fires. A better way to do this is to create a subclass of UITableViewCell. Let's call it AssignmentCell. Give the subclass an assignment property, and move the labelTicked: method to AssignmentCell.
If you've designed the cell in your storyboard, you can add a tap recognizer to the label right in the storyboard, and wire the recognizer to the cell's labelTicked: method in the storyboard.
In your table view data source's tableView(_:cellForRowAtIndexPath:), after you've dequeued the cell, set its assignment property to the assignment for that row.

Swift: Are addTarget actions only allowing class methods?

I tried to add a target to a UIButton and stumbled upon a weird behaviour
if i try:
//h = a collection view header
switch myVar {
case "none":
h.button.addTarget(self, action: "buttonTapped:", forControlEvents: .TouchUpInside)
func buttonTapped(sender:AnyObject) {
sendFriendRequest(self.targetUser,nil
}
}
I get SIGABRT - with "selector not found"
but if I move the function out of the switch case and make it a method of my ViewController, everything works as expected.
Anyone has an explanation for that? Is is just not allowed or are there technical reasons?
Selectors don't need to be class functions, but they must visible to the object call the selector (i.e. can't be marked as private or inside a method).
You can call any method in the project by using other class's instance instead of self.
Check this answer for the details.
https://stackoverflow.com/a/33068386/2125010

Swift: Best way to get value from view

I have a custom UIView (called GridView) that I initialize and then add to a ViewController (DetailViewController). GridView contains several UIButtons and I would like to know in DetailViewController when those buttons are touched. I'm new to Swift and am wondering what is the best pattern to use to get those events?
If you want to do this with notifications, use 1:
func postNotificationName(_ notificationName: String,
object notificationSender: AnyObject?)
in the method that is triggered by your button. Then, in your DetailViewController, add a listener when it is initialized with 2:
func addObserver(_ notificationObserver: AnyObject,
selector notificationSelector: Selector,
name notificationName: String?,
object notificationSender: AnyObject?)
Both functions can be called from NSNotificationCenter.defaultCenter().
Another method would be to add callbacks which you connect once you initialize the GridView in your DetailViewController. A callback is essentially a closure:
var callback : (() -> Void)?
which you can instantiate when needed, e.g.
// In DetailViewController initialization
gridView = GridView()
gridView.callback = { self.doSomething() }
In GridView you can trigger the callback like this:
func onButton()
{
callback?()
}
The callback will only execute, if unwrapping succeeds. Please ensure, that you have read Automatic Reference Counting, because these constructs may lead to strong reference cycles.
What's the difference? You can connect the callback only once (at least with the method I've showed here), but when it triggers, the receiver immediately executes its code. For notifications, you can have multiple receivers but there is some delay in event delivery.
Lets assume your GridView implementation is like as follows:
class GridView : UIView {
// Initializing buttons
let button1:UIButton = UIButton(...)
let button2:UIButton = UIButton(...)
// ...
// Adding buttons to view
self.addSubview(button1)
self.addSubview(button2)
// ...
}
Now, we will add selector methods which will be called when a button is touched. Lets assume implementation of your view controller is like as follows:
class DetailViewController : UIViewController {
let myView:GridView = GridView(...)
myView.button1.addTarget(self, action: "actionForButton1:", forControlEvents: UIControlEvents.TouchUpInside)
myView.button2.addTarget(self, action: "actionForButton2:", forControlEvents: UIControlEvents.TouchUpInside)
// ...
func actionForButton1(sender: UIButton!) {
// Your actions when button 1 is pressed
}
// ... Selectors for other buttons
}
I have to say that my example approach is not a good example for encapsulation principles of Object-Oriented Programming, but I have written like this because you are new to Swift and this code is easy to understand. If you want to prevent duplicate codes such as writing different selectors for each button and if you want to set properties of your view as private to prevent access from "outside" like I just did in DetailViewController, there are much much better solutions. I hope it just helps you!
I think you better create a class called GridView that is inherited from the UIView. Then, you can connect all you UI element with you class as IBOutlet or whatever using tag something like that. Later on, you can ask the instance of GridView in DetailViewController so that you can connect as IBAction.
Encapsulation is one of the principles of OOP.

Can't link Refresh Control to IBAction in Swift

I am trying to get a Refresh Control to link to an IBAction function in my View Controller. When I ctrl-drag it only gives me the option to create an Outlet.
I added the Refresh Control simply by setting Refreshing to Enabled in the Attributes Inspector.
Everything else is set up properly as far as I'm aware, the View Controller is set in the Identity inspector and I'm attempting to link to the correct Class.
Any Advice
I know it's not your solution of your problem.But you can create Refresh Control programmatically.Like...
var refreshCntrl : UIRefreshControl!
refreshCntrl = UIRefreshControl()
refreshCntrl.tintColor = UIColor.whiteColor()
refreshCntrl.attributedTitle = NSAttributedString(string: "Loading...")
refreshCntrl.addTarget(self, action:("refreshControlValueChanged"), forControlEvents: UIControlEvents.ValueChanged)
atableView.addSubview(refreshCntrl)
//Method of RefreshControl
func refreshControlValueChanged(){
atableView.reloadData()
refreshCntrl.endRefreshing()
}
Happened to me too, it gives you an Outlet option because you didn't select the button as you thought but the bar. Hence, for letting it set an Action you need to higlight the Bar Button in your controller's top-down menu on the left column.
Hope it helps!

Resources