-
Notifications
You must be signed in to change notification settings - Fork 98
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Event handlers #35
Comments
I think you can use BlocksKit or something similar. The syntax is somewhat weird in Swift 3, but does the job for now. |
If you are in the scope of a componentView you could use the classic target-action pattern from UIKit in the configuration closure. ClosureKit or BlocksKit will work just fine as well. Will soon update the documentation with some examples. |
Hi, Actually from architecture point of view i think that the components should not handle actions at all that's because that i think the same component can be reused by multiple classes in the lifecycle of the app. What i did currently is to i created a delegate inside the component and my view controller registered to this delegate and i forward all actions and handle them in the view controller side instead of handling them in the component code. What do you guys think about this approach ? Thanks! |
I second @ranhsd's point. |
@ranhsd approach is the one I prefer for my applications too - but Render is not opinionated about how you'll architect your user interaction and such. I usually make the distinction between Node fragments (pure functions that returns a hierarchy) and ComponentView subclasses. ComponentViews usually expose a delegate with a bunch of callbacks that you might want to implement in your VCs. |
Let me showcase my concern. When running this code import Foundation
import UIKit
import Render
import BlocksKit
struct HelloWorldState: StateType {
let name: String
}
class HelloWorldComponentView: ComponentView<HelloWorldState> {
override init() {
super.init()
self.defaultOptions = [.preventViewHierarchyDiff]
}
required init?(coder aDecoder: NSCoder) {
fatalError("Not supported")
}
override func construct(state: HelloWorldState?, size: CGSize = CGSize.undefined) -> NodeType {
func button() -> NodeType {
return Node<UIButton> {
(view, layout, size) in
print("added to \(view)")
view.bk_addEventHandler({ sender in
print("triggered in \(view)")
}, for: .touchUpInside)
let radius: CGFloat = CGFloat(randomInt(16, max: 128))
view.backgroundColor = Color.green
layout.height = radius * 2
layout.width = radius * 2
layout.alignSelf = .center
}
}
func container() -> NodeType {
return Node<UIImageView>(identifier: String(describing: HelloWorldComponentView.self)) {
(view, layout, size) in
view.backgroundColor = Color.black
view.isUserInteractionEnabled = true
let h = size.height == 0 ? CGFloat.max : size.height
let w = size.width == 0 ? CGFloat.max : size.width
layout.width = min(w, h)
layout.height = layout.width
layout.justifyContent = .center
}
}
return container().add(children: [
button()
])
}
} This is logged to console
When I tap the button I get
I really was expecting
Not at all what I expected from BlocksKit. It actually overwrites last registered block (if it is the same control event)! I think this is a bad architecture choice of BlocksKit, but luckily for users of Render + BlocksKit it Just Works™. :) Also totally unexpected that it works in the same manner for a UIGestureRecognizer convenience method due to requiring other recognizers to fail. Other frameworks than BlocksKit may choose a different philosophy. |
I also found a problem with BlocksKit import PlaygroundSupport
import Render
import BlocksKit
PlaygroundPage.current.needsIndefiniteExecution = true
struct AppState: StateType {
var isOn = false
}
class MyView: ComponentView<AppState> {
override func construct(state: AppState?, size: CGSize) -> NodeType {
return Node()
.add(children: [
Node<UIButton>() { [unowned self] button, layout, size in
button.setTitle("Click me", for: .normal)
button.setTitleColor(.black, for: .normal)
button.bk_(whenTapped: {
self.render(in: self.frame.size)
})
}
])
}
}
let container = UIView(frame: CGRect(x: 0, y: 0, width: 320, height: 568))
container.backgroundColor = .white
let view = MyView()
view.state = AppState()
view.render(in: container.frame.size)
container.addSubview(view)
PlaygroundPage.current.liveView = container The time it takes to render stuffs increases the more you tap the button. Looking from the source, every time you call |
Very good point. |
I'm surprised to find this in UIKit (though it makes sense here knowing that the function is not changing (as opposed to blocks (where you don't know wether it is the same block or not))):
In other words: this works fine override func construct(state: HelloWorldState?, size: CGSize = CGSize.undefined) -> NodeType {
return Node<UIButton>() { [unowned self] button, layout, size in
print("added to \(button)")
button.setTitle("Click me", for: .normal)
button.setTitleColor(.black, for: .normal)
button.addTarget(self, action: #selector(HelloWorldComponentView.tapped(_:)), for: .touchUpInside)
}
}
func tapped(_ sender: UIButton) {
print("triggered in \(sender)")
} |
You can always use a custom create closure in the node constructor for things you don't want to be reconfigured - this can also improved the component render time.
override func construct(state: HelloWorldState?, size: CGSize = CGSize.undefined) -> NodeType {
return Node<UIButton>(
identifier: "addItemButton",
create: {
let button = UIButton()
button.addTarget(self, action: #selector(Component.addItem), for: .touchUpInside)
},
configure: { [unowned self] button, layout, size in
button.setTitle("Click me", for: .normal)
button.setTitleColor(.black, for: .normal)
})
} |
Hi,
In additional i created the following protocol:
handle the events inside the component and then notify the delegate on each event (if was created) |
Awesome! |
Would be nice to add one of this examples to the Demo project :) |
Can you say something about reuse in this context? Anythings we should be aware of when using the create block? |
I'll add a proper section about reuse identifier in the README. As a general rule, reuse identifiers never hurt become they optimise the view reuse when the infra has to reconcile the view hierarchy. |
Hi @alexdrone , i will modify the example asap when i will find some time on next week |
I've added a todo list demo app where I explore some of the user interaction patterns in the context of unidirectional data flow. |
Hi @alexdrone , thanks for this demo it looks awesome.
If we will delete this script (and users will need to copy the frameworks that were generated by carthage on their own) we can make this project work with both cocoapods and carthage. |
Done that :) |
Hi,
I am considering to move from storyboards to Render for part of my app UI. Currently everything works fine for me and i managed to build some nice UI with it easily. The only thing that is missing is best practices on how to handle events. in my UI i have two buttons: one for signing in and one for signing up and i wanted to know what is the recommended way to handle the on tap event with Render.
Please consider the fact that ReSwift is also big part in my architecture so when the user will click on sign in then i will dispach an action.
Thanks.
The text was updated successfully, but these errors were encountered: