Skip to content

Instantly share code, notes, and snippets.

@pm-dev
Last active August 29, 2019 17:56
Show Gist options
  • Save pm-dev/a39269712d97896c7c9fb30bad940552 to your computer and use it in GitHub Desktop.
Save pm-dev/a39269712d97896c7c9fb30bad940552 to your computer and use it in GitHub Desktop.
/**
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)
}
}
@pm-dev
Copy link
Author

pm-dev commented Aug 29, 2019

Sample comment

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment