Does anyone know how RX button tap handling actually had been implemented?
If we looked in depth into RxCocoa code. To be more specific into the "Reactive" struct, we could find it has an extension where it's base is UIButton and in this extension, there is a tap variable of type ControlEvent which return a controlEvent(.touchUpInside).
So the question is how controlEvent(.touchUpInside) handles the control events?!
controlEvent(_ controlEvents: UIControlEvents) -> ControlEvent<()> .. is a function inside extenion for "Reactive" struct where it's base is UIControl.
And when our base is UIButton, which also extend UIControl, So we can call this function too when our base is UIButton and this is our case and this the function which handles UIButton taps (which only specified in RxCocoa as in extension).
How controlEvent function work and handle touchupInside?!
controllEvent just adding target selector to UIControll throw custom ControlTarget class in RxCocoa which pass escaping Callback to UIControll to emit onNext in a specific control event (touchupInside in our case).
read this classes in RxCoca if my explanation is not good enough :)
UIButton+Rx.swift
ControlEvent.swift
UIControl+Rx.swift
ControlTarget.swift
extension Reactive where Base: UIButton {
/// Reactive wrapper for `TouchUpInside` control event.
public var tap: ControlEvent<Void> {
return controlEvent(.touchUpInside)
}
}
Related
In a couple of my projects I think I'm not created a great structure in many cases.
It could be a game where I've created a game board (think about chess) with a grid of 8 * 8 cells. Each cell has a gesture recognizer that relies on a subclass (cell.swift), with the game logic in a parent ViewController.
For arguments sake, let us say we want to display to the user which square they have touched.
I've found out how to do this from the subclassed UIView (obvs. create the alert in the subclassed UIView / cell.swift in this example)
UIApplication.shared.keyWindow?.rootViewController?.present(alertController, animated: true, completion: nil)
but it seems to break the structure of the app - but wouldn't it be the same accessing an action in the parent ViewController? What is the best way of approaching this>
Your rootViewController is the VC on the bottom of your stack. It's not a safe way to access the visible VC, and is rarely useful, in general (there are cases, but I doubt your app would find them useful).
What you likely want to use is a delegate pattern. Let's say the parent VC that displays your chess board (let's call this MyBoardViewController), conforms to a protocol like the following. MyView is whatever custom UIView class you're using for the chess squares:
protocol SquareAlertHandler {
func handleSquarePressed(sender : myView)
}
And add the following property to your MyView class:
weak var delegate : SquareAlertHandler?
And replace whatever event handler you're currently using, with the following (I'm assuming you're using a UIButton in IB to handle the press, and have arbitrarily named the outlet 'didPress:'):
#IBAction didPress(sender : UIButton) {
delegate?.handleSquarePressed(self)
}
Now, add the protocol to your MyBoardViewController, and define the method:
class MyBoardViewController : UIViewController, SquareAlertHandler {
... ... ...
func handleSquarePressed(sender : myView) {
// Do something to handle the press, here, like alert the user
}
... ... ...
}
And finally, wherever you create the MyView instances, assign the MyBoardViewController instance as the delegate, and you're good to go.
Depending on your Swift literacy, this may be confusing. Adding code, so that I can at least match up the class names, would help to clarify things.
When should I use anyObject insted of UIButton in swift?
I am making an IBAction for my button that will be used to do more than on task on of the tasks is to switch to the next view.
Ultimately, it really doesn't matter.
You can choose to use the parameter of (sender: AnyObject) or you can use (sender: UIButton).
There might be times however where you might have to cast AnyObject as a UIButton if you need access to the properties provided by UIButton.
For example let's say you have and you want the button to disappear after it is clicked.
func doSomething(sender: AnyObject) {
let button: UIButton = sender as! UIButton
button.hidden = true
}
The purpose of using an abstract AnyObject type for an IBAction may be advantage for a situation in which you have multiple UI objects that should trigger the same action. An example of this would be if you wanted to have a button and a gesture recognizer share the functionality of a common action. Even with a shared action, it would be possible to have different execution paths for the two objects.
I use the next code to setup UIPanGestureRecognizer in my component:
MyComponent *c =[super newWithView:{
[UIView class],
{
{
CKComponentGestureAttribute(
[UIPanGestureRecognizer class],
&setupPanRecognizer,
#selector(panGesture:context:),
{}
)
},
{
#selector(setUserInteractionEnabled:), #YES
}
}
}
component: [MyOtherComponent newOtherComponentWithMode:model context:context]];
I process panGesture:context in MyComponentController object.
My problem is that UIPanGestureRecognizer blocks feed view from scrolling. In order to fix this I want to use UIGestureRecognizerDelegate protocol and allow both (scroll view and my pan) recognizers to work simultaneously.
My question is how can I assign my component controller as a delegate for UIPanGestureRecognizer? setupPanRecognizer is just a C function and it does not have reference to MyComponentController object or even the component itself.
The only way I see now is to get list of gesture recognisers somewhere in didUpdateComponent method in my controller, find the right one and assign delegate there.
Does ComponentKit provide any solution to this?
CKComponentViewAttributeValue CKComponentGestureAttribute(Class gestureRecognizerClass,
CKComponentGestureRecognizerSetupFunction setupFunction,
CKComponentAction action,
CKComponentForwardedSelectors delegateSelectors)
You can find the "delegateSelectors" argument at last, so you could try to pass in a vector of selectors which you would like to response in the controller.
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
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.