Last active
August 29, 2019 17:56
-
-
Save pm-dev/a39269712d97896c7c9fb30bad940552 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
Here's an example of how data flows from UI to/from the data model | |
with the current implementation. | |
*/ | |
class ActionBar : UIView { | |
var handle: Any? | |
weak var delegate: ActionBarDelegate? | |
weak var dataSource: ActionBarDataSource? { | |
didSet { | |
reloadData() | |
} | |
} | |
func reloadData() { | |
if self.dataSource == nil { | |
// render view with some 'empty' state | |
} else { | |
let numberOfReplies = self.dataSource.numberOfReplies(for: self) | |
let numberOfReactions = self.dataSource.numberOfReactions(for: self) | |
// render view | |
} | |
} | |
private func replyButtonWasTapped() { | |
self.delegate?.actionBarDidPressReply(self) | |
reloadData() | |
} | |
private func reactionButtonWasTapped() { | |
self.delegate?.actionBar(self, didSelectReaction: self.reactionButton.currentReaction) | |
reloadData() | |
} | |
} | |
class ActionBarDataSource: NSObject { | |
private let postController: PostController | |
init(postController: PostController) { | |
self.postController = postController | |
} | |
func numberOfReplies(for actionBar: ActionBar) -> Int { | |
let postId = actionBar.handle as? Int | |
if postId == nil { | |
return 0 | |
} | |
let post = self.postController.getPostWithId(postId!) | |
if post == nil { | |
return 0 | |
} | |
return post.commentCount.intValue | |
} | |
func numberOfReactions(for actionBar: ActionBar) -> Int { | |
let postId = actionBar.handle as? Int | |
if postId == nil { | |
return 0 | |
} | |
let post = self.postController.getPostWithId(postId!) | |
if post == nil { | |
return 0 | |
} | |
return post.numberOfReactions | |
} | |
} | |
class ActionBarDelegate { | |
private let postController: PostController | |
private weak var presentingViewController: UIViewController? | |
init(postController: PostController, presentingViewController: UIViewController?) { | |
self.postController = postController | |
self.presentingViewController = presentingViewController | |
} | |
func actionBarDidPressReply(_ actionBar: ActionBar) { | |
let postId = actionBar.handle as? Int | |
if postId == nil { | |
return | |
} | |
let post = self.postController.getPostWithId(postId) | |
if post == nil { | |
return | |
} | |
let replyViewController = StoryDetailVC(storyId: post.entityID) | |
self.presentingViewController?.navigationController?.pushViewController(replyViewController, animated: true) | |
} | |
func actionBar(_ actionBar: ActionBar, didSelectReaction: Reaction) { | |
let postId = actionBar.handle as? Int | |
if postId == nil { | |
return | |
} | |
let post = self.postController.getPostWithId(postId) | |
if post == nil { | |
return | |
} | |
self.postController.addReaction(reaction, to: post) { | |
actionBar.reloadData() | |
} | |
actionBar.reloadData() | |
} | |
} | |
class PostController { | |
func getPostWithId(_ id: Int) -> Post? { | |
// fetch post by id | |
} | |
func addReaction(_ reaction: Reaction, to post: Post, completion: () -> Void) { | |
// propegate through to server and local datamodel | |
} | |
} | |
class NewsfeedViewController { | |
private let actionBarDelegate: ActionBarDelegate | |
private let actionBarDataSource: ActionBarDataSource | |
init(actionBarDelegate: ActionBarDelegate, actionBarDataSource: ActionBarDataSource) { | |
self.actionBarDelegate = actionBarDelegate | |
self.actionBarDataSource = actionBarDataSource | |
} | |
func createCellForRow(_ row: Int) { | |
let cell = // dequeue cell | |
let post: Post = // find post for row | |
cell.actionBar.handle = post.entityID | |
cell.actionBar.delegate = actionBarDelegate | |
cell.actionBar.dataSource = actionBarDataSource | |
} | |
} | |
/** | |
Here's an example of that I believe improves on the current implementation | |
Key differences: | |
- no `handle` property. Since the dataSource & delegate | |
(renamed to state & eventHandler due to apple conventions of delegate/dataSource being weak) | |
are now always 1 to 1 with a view, there's no need for `handle` to mark what data this view is displaying. | |
- `reloadData` is private. It only gets triggered from new state emitted from the observable | |
- state/eventHandler functions that take the view as a param is the exception rather than the rule. | |
This is ok because it's no longer necessary to call `reloadData` on the view. | |
*/ | |
class ActionBar : UIView { | |
var eventHandler: ActionBarEventHandler | |
var state: Observable<ActionBarState> { | |
didSet { | |
state.subscribe(onNext: self.reloadData) | |
} | |
} | |
// Notice how reloadData is now private. It's now clear this is only called internally | |
private func reloadData(from state: ActionBarState) { | |
let numberOfReplies = state.numberOfReplies | |
let numberOfReactions = state.numberOfReactions | |
// render view | |
} | |
private func replyButtonWasTapped() { | |
self.eventHandler.onReply() | |
} | |
private func reactionButtonWasTapped() { | |
self.eventHandler.onReactionSelected(self.reactionButton.currentReaction) | |
} | |
} | |
class ActionBarState { | |
private let post: Post | |
init(post: Post) { | |
self.post = post | |
} | |
var numberOfReplies: Int { | |
return self.post.commentCount.intValue | |
} | |
var numberOfReactions: Int { | |
return self.post.numberOfReactions | |
} | |
} | |
class ActionBarEventHandler { | |
private let post: Post | |
private let postController: PostController | |
private weak var presentingViewController: UIViewController? | |
init(post: Post, postController: PostController, presentingViewController: UIViewController?) { | |
self.post = post | |
self.postController = postController | |
self.presentingViewController = presentingViewController | |
} | |
func onReply() { | |
let replyViewController = StoryDetailVC(storyId: self.post.entityID) | |
self.presentingViewController?.navigationController?.pushViewController(replyViewController, animated: true) | |
} | |
func onReactionSelected(_ reaction: Reaction) { | |
self.postController.addReaction(reaction, to: self.post) | |
} | |
} | |
class PostController { | |
func addReaction(_ reaction: Reaction, to post: Post, completion: () -> Void) { | |
// propegate through to server and local datamodel | |
} | |
} | |
class NewsfeedViewController { | |
private let postController: PostController | |
init(postController: PostController) { | |
self.postController = postController | |
} | |
func createCellForRow(_ row: Int) { | |
let cell = // dequeue cell | |
let post: Post = // find post for row | |
let postObservable: Observable<Post> = // post observable for row | |
cell.actionBar.state = postObservable.map { ActionBarState($0) } | |
cell.actionBar.eventHandler = ActionBarEventHandler(post: post, | |
postController: postController, | |
presentingViewController: self) | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Sample comment