I am new to Xcode but have been developing for a 15 years. I am trying to understand how event declaration works in Xcode. Could it possibly be that you can only declare when you Ctr drag it to the code? And then only view it via the dialog box in the Connection Viewer? That would be terribly annoying. What am I missing? Surely the event should appear in the code somewhere.
Here is a sample function that is supposedly declaring a "Did End on Exit" code.
#IBAction func helloAction(sender: UITextField) {
nameLabel.text = "Hi \(sender.text)"
}
Thanks for your help.
The helloAction method is not declaring a "Did End on Exit" code, unless I'm misunderstanding what you mean by declaring. helloAction is a target that's called when the control it's bound to in Interface Builder receives a Did End on Exit event.
This uses a mechanism known as the Target-Action pattern in Cocoa. A control receives an event, and then calls any methods that have registered to be notified when the control receives that event.
This relationship can be created programmatically as well. Consider the following.
let button = UIButton()
button.addTarget(self, action: "tapButton:", forControlEvents:UIControlEvents.TouchUpInside)
When the button is tapped and released with the touch still inside the buttons bounds, self's tapButton: method is called.
If you want to learn more about Target-Action in Cocoa, check out a high level overview or the definitive discussion, both from Apple.
Related
UIButton
I have a View Controller in which the user needs to enter in mission level data. The view controller then has a custom UIView (Crew View) in its view hierarchy where there is a button that the user can press to add Persons to the mission. Those persons are custom UIViews as well (Person Views) that get added as children to the Crew View.
The layout looks something like this:
VC
|_ScrollView
|EntryFields
|_ Crew View (UIView subclass)
|UIButton
|_ Person View (UIView subclass)
|EntryFields
|UIButton
|_ Person View
|EntryFields
|UIButton
...
When there is no first responder for the keyboard, all the buttons pressed trigger their respective handlers regardless of where they are in their View Hierarchy.
Now if an entry field in the PersonView is active I'm able to use both the UIButton in the PersonView as well as the UIButton higher up in the Crew View.
But if an entry field in the top level is active, the handler for the buttons are not called.
Note: I know the buttons are receiving the touch events because their UI changes since the .highlighted state gets activated. The button handlers are set to .touchUpInside.
UISwitch
This has also been an issue in another part of my app with a UISwitch. The view hierarchy looks like this:
VC
|_ScrollView
|Entry Fields
|_Some UIView Subclass
|UISwitch
When the keyboard was active in the scenario (from the Entry Fields up the view hierarchy), the UISwitch would not call it's handler (set on .valueChanged).
My Solution: I removed the target handler from the UISwitch and instead set a tap gesture recognizer for the whole view that would manually trigger the switch, this solution worked regardless if there was an active first responder up the view hierarchy.
Has anybody experienced this before? Why are the event target handlers not being called on the UIControls(yet they're still responding in the UI) when there are active first responders in their superview but gesture recognizers work just fine.
Everything is built programmatically, not that it should matter and the View Hierarchy debugger on XCode shows that the UIControls are not being blocked.
More Information
So the EntryFields is actually a custom component (to follow material design with a floating placeholder). The view hierarchy of the EntryField is as follows:
EntryField (UIView subclass)
|_ StackView
|_ UILabel
|_ UITextField
Everything in this project is done programmatically with auto layout constraints. The EntryField interacts with its delegates by forwarding the protocol methods from the UITextField. I don't know if this is messing with the first responder chain in the application.
Here's a sample project (gutted version of the production code) that replicates it perfectly.
https://github.com/barbulescualex/55051678
I've checked your code and found issue in your button declaration. you need to use lazy in order to get the events.
i.e.
lazy var addButton : UIButton = {
let button = UIButton()
button.setTitle("Add Person", for: .normal)
button.setTitleColor(UIColor.purple, for: .normal)
button.setTitleColor(UIColor.green, for: .highlighted)
button.translatesAutoresizingMaskIntoConstraints = false
button.addTarget(self, action: #selector(addPersonField(_:)), for: .touchUpInside)
button.translatesAutoresizingMaskIntoConstraints = true
return button
}()
output
https://drive.google.com/open?id=1_u4a3anvOPuZGlJ3VtM15D6aBdXsclMm
self: The self keyword refers to the current class instance, from
within that class instance.
func addTarget(_ target: Any?,
action: Selector,
for controlEvents: UIControl.Event)
Target: the object whose action method is called.
Why would self in the closure work and refer to my view controller if clicked on normally vs
it not working if there's an active textfield in the superview:
From the docs
The control does not retain the object in the target parameter. It is
your responsibility to maintain a strong reference to the target
object while it is attached to a control.
i.e. It depends upon the current state of instance (Life Cycle) and how the iOS handle it.
When we use let myButton: UIButton = {...}() you're immediately assigning value to myButton variable there are chances that 'self' hasn't been initialized by the OS
.In order to make sure when we addTarget to our button object, 'self' is initialized properly we use lazy keyword
Lazy: Lazy initialization (also sometimes called lazy instantiation, or lazy loading) is a technique for delaying the creation of an object or some other expensive process until it’s needed.
When programming for iOS, this is helpful to make sure you utilize only the memory you need when you need it.
With lazy we are making sure that our control will have a strong reference to current class which is going to handle the action
I am not sure I fully understand the issue you're experiencing, so I did a little demo that should reproduce yours, even though I am not experiencing the same behaviour as you do, at least not for now.
Check it out (video) and let's see if it's on you or not.
Now, I have the following hierarchy in my demo project:
And when I run it it looks like this:
Color legend:
RED: VC's View (self.view)
BLUE: ScrollView
GREEN: Crew View
I have no issues at all as you can see in the video, what's different from yours?
LATER EDIT:
Ok, the issue is more than meets the eye, the good news is that there's no issue, this is just how iOS works and given this opportunity I am going to explain as detailed as possible what really happens.
Firstly, your question was why it works with the bottom textFields but it doesn't work with the top ones.
ANSWER:
You are messing it when you declare the addButton as a let instead of a lazy var, then you add as a target self, which in that scope is the closure itself and after you exit the scope, if you place a breakpoint in func addPersonField you should see that sender.allTargets has no targets. That's ok with the compiler since you can, there was a time when you couldn't do it, but now you can because target is declared as Any?, which means you could even set nil as a target and you'll experience the same behaviour.
Now, you might wonder why it works with self as a closure which gets deallocated after you exit the scope, or why it works just as fine with nil. The doc for addTarget(_:action:for:)
says that:
If you specify nil, UIKit searches the responder chain for an object
that responds to the specified action message and delivers the message
to that object.
Which is your PersonsView, which doesn't get deallocated since is always on screen and has the specified action your func addPersonField. That's why it works before you start using any textfields (I know it works with the bottom ones, I'll get there).
Why it works with the bottom textFields you'll wonder, right? Well, again... you're doing the magic here without knowing, if you tap on a bottom textField that object becomesFirstResponder, now when an event occurs (like your .touchUpInside on the Add Person button) if the firstResponder can't handle it, UIKit sends the event to the text field’s parent UIView object, which in this case is the stackView, if the stackView can't handle it, the event is sent to the stackView's parent UIView which is exactly the PersonsView - which is the golden one, because it responds to the selector you specified addPersonField(_:).
See the personsView below:
On the other hand, when you tap on the TOP textFields (those EntryFields), they are embedded by a horizontal stackView which is embedded by a vertical stackView, which is contained by the ScrollView. Now, if you followed me til here, you got the idea, the Responder Chain goes from EntryField -> Horizontal StackView -> Vertical StackView -> ScrollView -> etc but it doesn't look in the other stackViews that contain your PersonsView, where you defined the selector, that's why it doesn't work here.
See how the stacks are on the same level, embedded in the same VerticalStackView which is embedded in the ScrollView below:
Even though, if you tap on a Top EntryField/TextField, and press return on the keyboard, you call view.textField.resignFirstResponder() which enables the Add Person button again.
I am subclassing UIControl and as I wanted to set my view controller as the target, I discovered the sendAction method. I can't really figure out the differences between both methods and when their respective usage is more appropriate.
The difference is that sendAction(_:to:for:) actually calls the defined selector right away, while addTarget(_:action:for:) only associates a target and action with the control and only calls the selector when the event happens.
You'd use sendAction:to:forEvent: to simulate an actual, under-the-hood system call. ie: Simulating a user actually tapping a UIButton (a UIControl) and sending the UIControlEvents touchUpInside to the target. I've personally never had to use it before.
addTarget:action:forControlEvents: maps the Selector, UIControlEvent and Target, to be used at a later time, such as when a user taps a UIButton.
The problem
On iOS 10.2
didSelect() is only called when select a message for the first time, but not for the second time selecting the same message (right after the first select happened). For example:
Click a received MSMessage Message_A while my message app is active, didSelect() method is correctly called and app transit to extended view.
Click down arrow to bring app back to compressed view.
Click the same message - Message_A again, this time didSelect() isn't triggered.
Words From Apple
func didSelect(MSMessage, conversation: MSConversation)
Invoked after the system updates the conversation’s selectedMessage property in response to the user selecting a message object in the transcript.
My thought
It seems selectedMessage isn't updated when we click that message the second time (because it was already set in the first click), thus didSelect() isn't called.
Question
Am I doing it wrong?
Is there a way to work around and call didSelect() as long as a selection happens? selectedMessage is read only...
Or is there a way to make message expire (disappear) from conversation immediately after user opens (clicks) the message?
I'm afraid it is a bug, there's an open radar for that (or it is done 'by design', taking into account how much time passed since the issue had been filed).
Nevertheless, when message is selected, iMessage's extension is trying to move to expanded mode and calls willTransition(to presentationStyle:) delegate method (which appears to be another bug or cool-thing-by-design). By checking whether the expanded controller has been already shown and tuning your custom flags you may do the trick, although it is not reliable in some cases.
I have the same problem, the didSelect() and willSelect() methods are called only once.
I circumvented this problem by implementing my logic in the method :
Objective C
-(void)didBecomeActiveWithConversation:(MSConversation *)conversation
Swift
func didBecomeActive(with conversation: MSConversation)
#degapps,
Here is a workaround: After first click on message, didSelect() will take you to expanded view. Now, if a transition to compact view happens, we dismiss this app. It's not a good solution and unlikely to work for most of applications.
override func willTransition(to presentationStyle: MSMessagesAppPresentationStyle) {
if (presentationStyle == .compact) {
if let _ = self.activeConversation?.selectedMessage {
self.dismiss()
}
}
}
can anyone say how do i observe for action in Reactive Cocoa for a UIButton or UIControl..
An alternative way is to bind the view to the view model.And observe changes on the Mutable Property.
I tried using below code but none is firing.
self.rollBtn.reactive.trigger(for: .touchUpInside).observeValues {
value in
print(value)
}
EDIT: Actually i am trying to get the sender on button Tap..how can i do that?
You have done nothing wrong in this code snippet - trigger(for:) is one of the ways to get notified in RAC 5.0. It should print () for every press on the button.
Have you linked the button with the view, if you are using Storyboard or Interface Builder? Where did you place this piece of code? Make sure you place it in viewDidLoad or awakeFromNib so that it gets called before the view is presented.
-
EDIT: Actually i am trying to get the sender on button Tap..how can i do that?
As mentioned in the comments, trigger(for:) returns a Signal<(), NoError>. It doesn't include the sender with the value event. You would need to reference the sender manually, e.g.:
button.reactive
.trigger(for: .touchUpInside)
.observeValues { [unowned button] in
_ = button
}
When defining an IBAction, there's an option for Anyobject and UIButton, both works, what's the difference?
Yes, both works. The difference is just that by declaring it to a button, you get a typecasted reference as UIButton instead of AnyObject (or id in Objective-C). If you did not do that you will have to do it manually inside the code.
You should prefer leaving it to AnyObject in case the action has some code you would like to call from anywhere else, rather than just the button action.
For example a refresh button, you might have to do a refresh programatically. If you have set your action parameter to UIButton, you have to send an UIButton (but why??? You are doing it programatically, right?). Whereas if it is AnyObject, you can send 'self'. Which will also make you distinguish whether the refresh was done on action or programatically.
Hope that explains.
Sorry for no code. I am not into Swift coding much. Edits are welcome.
EDIT
So, even if it is AnyObject, it does have your UIButton properties. Just type cast it to use them.
If you are only ever going to use the function with a UIButton it is best practice to declare your sender as a UIButton. This saves you a bit of code and it also tells anyone in the future reading your code that you only expect the function to be used with a UIButton.
Using AnyObject or Any will work, but you will need to do an guard let button = sender as? UIButton { return } in order to access it as a button. This way allows you to react differently depending on what the sender actually is, but I don't recommend doing that.
If it's anyObject, then it doesn't have any of UIButton's properties. When you click the button and the IBAction fires, sender contains information about the thing that triggered the action.
For example, for a UIButton, you might want to query the UIButton's text when the IBAction triggers.
However, in a situation where the IBAction is connected to two different UI controls, let's say, a button and a slider, querying the sender when it's of type UIButton (while the triggering UI element is the UISlider) will crash the program. If you have AnyObject, you'll be able to test if the sender is a UIButton or a UISlider, and then do something.
In general, if you don't care about sender, leave it blank, so people reading your code will know that you aren't using sender for anything.