åšå¢ã®ãããã©ãã«éå§
ãããŠãæ¬åœã«ããã§ãã ç§ã察åŠããªããã°ãªããªãã£ãæãé£ããããšã¯ãããã°ã©ã ã³ãŒãã®ãŸã£ããç°ãªãæ§æã§ããã ç§ã®åœä»€åããã°ã©ãã³ã°ã®çµéšã§ã¯ãæ°ããæ¹æ³ã§åæ§ç¯ããããšã¯å°é£ã§ããã ããããç§ã®æ¬èœã¯ããããæŽçãã䟡å€ããããšç§ã«èšã£ãã ReactiveXã®æ¬è³ªãç解ããã®ã«2é±éã®
ããã§ã¯å§ããŸãããã Facebookã§å£ã®ç°¡åãªãªãŒããŒãäœæããŸãã ãããè¡ãã«ã¯ã RxSwift ãããŒã¿ãããã³ã°çšã®ObjectMapper ã Facebook iOS SDK ãããã³è² è·ã瀺ãMBProgressHUDãå¿ èŠã§ãã Xcodeã§ãããžã§ã¯ããäœæããäžèšã®ã©ã€ãã©ãªãããã«æ¥ç¶ãïŒ CocoaPodsã䜿çšïŒã æ瀺ã«åŸã£ãŠFacebookãšã®ãªã³ã¯ãæ§æããã³ãŒãã£ã³ã°ã«é²ã¿ãŸãã
ãã°ã€ã³ç»é¢
ãã€ãŒã«ãåçºæããã®ã§ã¯ãªããFacebookã®æ¢è£œã®ãã¿ã³ãç»é¢ã®äžå€®ã«é 眮ããã ãã§ã-FBSDKLoginButtonïŒ
let loginButton = FBSDKLoginButton() loginButton.center = self.view.center loginButton.readPermissions = ["user_posts"] loginButton.delegate = self
ãã°ã€ã³ãã¿ã³ã®FBSDKLoginButtonDelegateããªã²ãŒããè¿œå ããããšãããã³ããªã²ãŒãã¡ãœãããå®è£ ããããšãå¿ããªãã§ãã ããã
// MARK: Facebook Delegate Methods func loginButton(loginButton: FBSDKLoginButton!, didCompleteWithResult result: FBSDKLoginManagerLoginResult!, error: NSError!) { if ((error) != nil) { // Process error let alert = UIAlertController(title: "", message: error.localizedDescription, preferredStyle: .Alert) alert.addAction(UIAlertAction(title: "OK", style: .Cancel, handler: nil)) self.presentViewController(alert, animated: true, completion: nil) } else if result.isCancelled { let alert = UIAlertController(title: "", message: "Result is cancelled", preferredStyle: .Alert) alert.addAction(UIAlertAction(title: "OK", style: .Cancel, handler: nil)) self.presentViewController(alert, animated: true, completion: nil) } else { let storyboard = UIStoryboard(name: "Main", bundle: nil) let vc = storyboard.instantiateViewControllerWithIdentifier("navController") as! UINavigationController self.presentViewController(vc, animated: true, completion: nil) } } func loginButtonDidLogOut(loginButton: FBSDKLoginButton!) { print("User Logged Out") }
ããã§ã¯ãã¹ãŠãç°¡åã§ã-ãã°ã€ã³ãšã©ãŒãŸãã¯ãŠãŒã¶ãŒãFacebookæ¿èªç»é¢ã§[ãã£ã³ã»ã«]ãã¿ã³ãã¯ãªãã¯ããå Žåãããã«é¢ããã¡ãã»ãŒãžãã¢ã©ãŒãã®åœ¢åŒã§è¡šç€ºããŸãããã¹ãŠãåé¡ãªããã°ããã¥ãŒã¹ãªã¹ããšå ±ã«æ¬¡ã®ç»é¢ã«éä¿¡ããŸãã ãã°ã¢ãŠãæ©èœã«ã¯è§ŠããŸããã§ããã ã芧ã®ãšããããããŸã§ã®ãšããããã¹ãŠã¯ååã«ç°¡åã§ãããåå¿æ§ã«ã€ããŠã®è©±ã¯ãããŸããã ããã«ã¯ããªã埮åŠãªç¹ããããŸã-KISSã®åå
ãã¥ãŒã¹ç»é¢
FacebookãŠã©ãŒã«ãããã¥ãŒã¹ã®ãªã¹ããååŸããé¢æ°ãäœæããŸãããã®æ»ãå€ã®ã¿ã€ãã¯Observableã§ãã
func getFeeds() -> Observable<GetFeedsResponse> { return Observable.create { observer in let parameters = ["fields": ""] let friendsRequest = FBSDKGraphRequest.init(graphPath: "me/feed", parameters: parameters, HTTPMethod: "GET") friendsRequest.startWithCompletionHandler { (connection, result, error) -> Void in if error != nil { observer.on(.Error(error!)) } else { let getFeedsResponse = Mapper<GetFeedsResponse>().map(result)! observer.on(.Next(getFeedsResponse)) observer.on(.Completed) } } return AnonymousDisposable { } } }
ãã®ã³ãŒãã¯ã©ããªããŸããïŒ ãme / feedããã¥ãŒã¹ãåä¿¡ããããã«ãããã¯ãŒã¯èŠæ±FBSDKGraphRequestãçæãããåŸãã³ãã³ããçºè¡ããŠèŠæ±ãå®è¡ããå®äºãããã¯ã®ã¹ããŒã¿ã¹ãç£èŠããŸãã ãšã©ãŒãçºçããå Žåã¯Observableã«æž¡ããæåããå Žåã¯åä¿¡ããããŒã¿ãObservableã«è»¢éããŸãã
泚ïŒå€æ°ãFBSDKGraphRequestã«æž¡ããŸã
let parameters = ["fields": ""]
ãã©ã¡ãŒã¿ã®ç©ºã®ã»ããã§ã ããã¯Facebookãã¢ããªã±ãŒã·ã§ã³ã®äœæããã»ã¹ããå°ãæ»ã£ãŠãããŒã¿ãããã³ã°ã«ã€ããŠèª¬æããŸãããã ObjectMapperã§ãã®åé¡ã解決ããŸããããã«ãããéåžžã«è¿ éãã€ç°¡åã«ãããè¡ãããšãã§ããŸãã
class GetFeedsResponse: Mappable { var data = [Feed]() var paging: Paging! required init?(_ map: Map){ } // Mappable func mapping(map: Map) { data <- map["data"] paging <- map["paging"] } } class Feed: Mappable { var createdTime: String! var id: String! var story: String? var message: String? required init?(_ map: Map){ } // Mappable func mapping(map: Map) { createdTime <- map["created_time"] id <- map["id"] story <- map["story"] message <- map["message"] } } class Paging: Mappable { var next: String! var previous: String! required init?(_ map: Map){ } // Mappable func mapping(map: Map) { next <- map["next"] previous <- map["previous"] } }
ãã¥ãŒã¹ã«é¢ãã詳现ãªæ å ±ãåãåãããã«ãããã«ãããã¯ãŒã¯ãªã¯ãšã¹ããäœæããããšããå§ãããŸãã
func getFeedInfo(feedId: String) -> Observable<GetFeedInfoResponse> { return Observable.create { observer in let parameters = ["fields" : "id,admin_creator,application,call_to_action,caption,created_time,description,feed_targeting,from,icon,is_hidden,is_published,link,message,message_tags,name,object_id,picture,place,privacy,properties,shares,source,status_type,story,story_tags,targeting,to,type,updated_time,with_tags"] let friendsRequest = FBSDKGraphRequest.init(graphPath: "" + feedId, parameters: parameters, HTTPMethod: "GET") friendsRequest.startWithCompletionHandler { (connection, result, error) -> Void in if error != nil { observer.on(.Error(error!)) } else { print(result) let getFeedInfoResponse = Mapper<GetFeedInfoResponse>().map(result)! observer.on(.Next(getFeedInfoResponse)) observer.on(.Completed) } } return AnonymousDisposable { } } }
ã芧ã®ãšããããã©ã¡ãŒã¿ãŒå€æ°ã«å€æ°ã®ãã£ãŒã«ããæž¡ããŸããããããã¯ãã¹ãŠããã¥ã¡ã³ãã«å«ãŸããŠãããã£ãŒã«ãã§ãã ãã¹ãŠã䞊ã¹æ¿ããã®ã§ã¯ãªããäžéšã®ã¿ã䞊ã¹æ¿ããŸãã ããŒã¿ãããã³ã°ã¯æ¬¡ã®ãšããã§ãã
class GetFeedInfoResponse: Mappable { var createdTime: String! var from: IdName! var id: String! var isHidden: Bool! var isPublished: Bool! var message: String? var name: String? var statusType: String? var story: String? var to = [IdName]() var type: String! var updatedTime: String! required init?(_ map: Map){ } // Mappable func mapping(map: Map) { createdTime <- map["created_time"] from <- map["from"] id <- map["from"] isHidden <- map["is_hidden"] isPublished <- map["is_published"] message <- map["message"] name <- map["name"] statusType <- map["status_type"] story <- map["story"] // It necessary that Facebook API have a bad structure // buffer%varname% is a temporary variable var bufferTo = NSDictionary() bufferTo <- map["to"] if let bufferData = bufferTo["data"] as? NSArray { for bufferDataElement in bufferData { let bufferToElement = Mapper<IdName>().map(bufferDataElement) to.append(bufferToElement!) } } type <- map["type"] updatedTime <- map["updated_time"] } } class IdName: Mappable { var id: String! var name: String! required init?(_ map: Map){ } // Mappable func mapping(map: Map) { id <- map["id"] name <- map["name"] } }
ã芧ã®ãšãããããã«ãè»èã®ããšããããŸãã ããšãã°ãjsonãªããžã§ã¯ããtoãã解æãããšãã«ããtoãã«ã¯åäžã®ãã£ãŒã«ããdataããå«ãŸããããã¯jsoné åã§ããããã次ã®ããã«åé¿ããå¿ èŠããããŸããã
var bufferTo = NSDictionary() bufferTo <- map["to"] if let bufferData = bufferTo["data"] as? NSArray { for bufferDataElement in bufferData { let bufferToElement = Mapper<IdName>().map(bufferDataElement) to.append(bufferToElement!) } }
åºæ¬çã«ã1ã€ã®ãããŒã¿ããã£ãŒã«ããæã€ãã¡ã€ã«ãäœæããããã§ãªããžã§ã¯ããéãã«ããã解é€ã§ããŸããã1ã€ã®ãã£ãŒã«ãããããããããã«æ°ãããã¡ã€ã«ãäœæããã®ã¯æããªããšã®ããã«æããŸããã ãã®åé¡ã«å¯Ÿãããããšã¬ã¬ã³ããªè§£æ±ºçããæã¡ã®æ¹ãããã°
çŸã«æ»ããŸãããã Facebookãšé£çµ¡ãåããObservableã®åœ¢åŒã§ãã¥ãŒã¹ã®ãªã¹ããååŸããŸããããã®ããŒã¿ã衚瀺ããå¿ èŠããããŸãã ãã®ç®çã®ããã«ã MVVMãã¿ãŒã³ã䜿çšããŸãã ReactiveXã®äœ¿çšãèæ ®ãããã®ãã³ãã¬ãŒãã®ç§ã®èªç±ãªè§£éã¯æ¬¡ã®ããã«èãããŸããViewModelã«ã¯ã€ãã³ããçæããObservableããããViewã¯ãããã®ã€ãã³ãã«ãµãã¹ã¯ã©ã€ãããŠåŠçããŸãã ã€ãŸããViewModelã¯ãã©ããŒããŠãããŠãŒã¶ãŒã«äŸåããŸãã-ãµãã¹ã¯ã©ã€ããŒãããå ŽåãObservableã¯ããŒã¿ãçæãããµãã¹ã¯ã©ã€ããŒãããªãå ŽåãObservableã¯äœãçæããŸããïŒããããããªObservableã®å Žåããã®ã¹ããŒãã¡ã³ãã¯trueã«ãªããŸãïŒåžžã«ããŒã¿ãçæããŸãïŒã ãã¥ãŒã¹ç»é¢ã®ViewModelãäœæããŸãã
class FeedsViewModel { let feedsObservable: Observable<[Feed]> let clickObservable: Observable<GetFeedInfoResponse> // If some process in progress let indicator: Observable<Bool> init(input: ( UITableView ), dependency: ( API: APIManager, wireframe: Wireframe ) ) { let API = dependency.API let wireframe = dependency.wireframe let indicator = ViewIndicator() self.indicator = indicator.asObservable() feedsObservable = API.getFeeds() .trackView(indicator) .map { getFeedResponse in return getFeedResponse.data } .catchError { error in return wireframe.promptFor(String(error), cancelAction: "OK", actions: []) .map { _ in return error } .flatMap { error in return Observable.error(error) } } .shareReplay(1) clickObservable = input .rx_modelSelected(Feed) .flatMap { feed in return API.getFeedInfo(feed.id).trackView(indicator) } .catchError { error in return wireframe.promptFor(String(error), cancelAction: "OK", actions: []) .map { _ in return error } .flatMap { error in return Observable.error(error) } } // If error when click uitableview - set retry() if you want to click cell again .retry() .shareReplay(1) } }
ã³ãŒãã解æããŸãããã å ¥åãã£ãŒã«ãã§ã¯ãViewã®ããŒãã«ãäŸåé¢ä¿ãã£ãŒã«ãã§ã¯APIã¯ã©ã¹ãšã¯ã€ã€ãã¬ãŒã ãæž¡ããŸãã ã¯ã©ã¹ã«ã¯3ã€ã®å€æ°ããããŸããfeedsObservableã¯ãã¥ãŒã¹ã®ãªã¹ããå«ãObservableãè¿ããclickObservableã¯ããŒãã«ã»ã«ãã¯ãªãã¯ããããã®ãã³ãã©ãŒã§ãããindicatorã¯èªã¿èŸŒã¿ã€ã³ãžã±ãŒã¿ãŒã衚瀺ãããã©ããã決å®ããããŒã«å€æ°ã§ãã ã³ãŒãã«ã¯2ã€ã®å¥œå¥å¿onceçãªã¯ã©ã¹ããããŸã-WireframeãšViewIndicatorããããã«ã€ããŠè©³ããèŠãŠãããŸãããã ã¯ã€ã€ãã¬ãŒã ã¯ãã¢ã©ãŒãã®äºåŸå¯Ÿå¿çãªå®è£ ã«ãããŸããã RxSwiftãªããžããªã®äŸãããã®å®è£ ãåããŸããã ViewIndicatorã¯è¿œè·¡äžã§ãããã§ãŒã³ã§å°ãªããšã1ã€ã®ã·ãŒã±ã³ã¹ãå®è¡ãããŠããéãããã®ã¯ã©ã¹ã®trackViewé¢æ°ãå®è¡ãããŸãããããã£ãŠãtrackViewã¯ããŒãã€ã³ãžã±ãŒã¿ãŒã®è¡šç€ºã«äŸ¿å©ã§ãã ãã®èšäºã§ã¯ã³ãŒããæäŸããŸããããããžã§ã¯ããªããžããªã§èŠã€ããããšãã§ããŸãããªã³ã¯ã¯èšäºã®äžéšã«ãããŸãã
Observableã®ããžãã¯ã«è§ŠããŠã¿ãŸãããã æåã®-feedsObservable-Facebookããã®å¿çãåä¿¡ããããããããã¯ã§ãã¥ãŒã¹ãªã¹ããååŸãããŠè¿ãããŸãã次ã«ããšã©ãŒåŠçãšãè² è·ã衚瀺ããtrackViewããããŸãã 2çªç®ã®clickObservableã¯ãããŒãã«ã»ã«ã®ã¯ãªãã¯ãç£èŠãããã®åŸããã¥ãŒã¹ã«é¢ãã詳现æ å ±ãæ±ãããããã¯ãŒã¯èŠæ±ãåŒã³åºããŸãã ã¹ãŒããŒãã¢ãã«ãå®æããããçŽæ¥ãã¥ãŒã«ç§»åããŸãã
ããã«Viewã³ãŒããæäŸããŸãããã®åŸãåæããŸãã
class FeedsViewController: UIViewController, UITableViewDelegate { @IBOutlet weak var feedsTableView: UITableView! var disposeBag = DisposeBag() override func viewDidLoad() { super.viewDidLoad() let viewModel = FeedsViewModel( input:feedsTableView, dependency: ( API: APIManager.sharedAPI, wireframe: DefaultWireframe.sharedInstance ) ) let progress = MBProgressHUD() progress.mode = MBProgressHUDMode.Indeterminate progress.labelText = " ..." progress.dimBackground = true viewModel.indicator .bindTo(progress.rx_mbprogresshud_animating) .addDisposableTo(self.disposeBag) feedsTableView.rx_setDelegate(self) viewModel.feedsObservable .bindTo(feedsTableView.rx_itemsWithCellFactory) { tableView, row, feed in let cell = tableView.dequeueReusableCellWithIdentifier("feedTableViewCell") as! FeedTableViewCell cell.feedCreatedTime.text = NSDate().convertFacebookTime(feed.createdTime) if let story = feed.story { cell.feedInfo.text = story } else if let message = feed.message { cell.feedInfo.text = message } cell.layoutMargins = UIEdgeInsetsZero return cell } .addDisposableTo(disposeBag) viewModel.clickObservable .subscribeNext { feed in let storyboard = UIStoryboard(name: "Main", bundle: nil) let feedInfoViewController = storyboard.instantiateViewControllerWithIdentifier("feedInfoViewController") as! FeedInfoViewController feedInfoViewController.feedInfo = feed self.navigationController?.pushViewController(feedInfoViewController, animated: true) } .addDisposableTo(disposeBag) } override func didReceiveMemoryWarning() { super.didReceiveMemoryWarning() } // Deselect tableView row after click func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) { tableView.deselectRowAtIndexPath(indexPath, animated: true) } }
ãŸããviewModelãäœæããŸãã 次ã«ãèªã¿èŸŒã¿ã€ã³ãžã±ãŒã¿ãŒãäœæããäœããã®æ¹æ³ã§ãããViewIndicatorã«æ¥ç¶ããå¿ èŠããããŸãã ãããè¡ãã«ã¯ãMBProgressHUDã®æ¡åŒµæ©èœãèšè¿°ããå¿ èŠããããŸãã
extension MBProgressHUD { /** Bindable sink for MBProgressHUD show/hide methods. */ public var rx_mbprogresshud_animating: AnyObserver<Bool> { return AnyObserver {event in MainScheduler.ensureExecutingOnScheduler() switch (event) { case .Next(let value): if value { let loadingNotification = MBProgressHUD.showHUDAddedTo(UIApplication.sharedApplication().keyWindow?.subviews.last, animated: true) loadingNotification.mode = self.mode loadingNotification.labelText = self.labelText loadingNotification.dimBackground = self.dimBackground } else { MBProgressHUD.hideHUDForView(UIApplication.sharedApplication().keyWindow?.subviews.last, animated: true) } case .Error(let error): let error = "Binding error to UI: \(error)" #if DEBUG rxFatalError(error) #else print(error) #endif case .Completed: break } } } }
MBProgressHUDã«å€ãæå®ãããŠããå Žåãã€ã³ãžã±ãŒã¿ãŒã衚瀺ããŸãã å€ãæå®ãããŠããªãå Žåã¯ãé衚瀺ã«ããŸãã ããã§ãMBProgressHUDãšViewIndicatorã®éã®ãã€ã³ãã£ã³ã°ãæ§æããå¿ èŠããããŸãã ããã¯æ¬¡ã®ããã«è¡ãããŸãã
viewModel.indicator .bindTo(progress.rx_mbprogresshud_animating) .addDisposableTo(self.disposeBag)
ãã®åŸãUITableViewèŠçŽ ãã¯ãªãã¯ããŠæ°ããç»é¢ã«ç§»åããã®ãšåæ§ã«ãUITableViewãšããŒã¿ã®åä¿¡ã®éã®ãã€ã³ãã£ã³ã°ãæ§æããŸãã ãŸãããåå¿æ§ãã®ãªãæçš¿ã«é¢ãã詳现æ å ±ã®ç»é¢ãäœæããŸããã
override func viewDidLoad() { super.viewDidLoad() var feedDetail = "From: " + feedInfo.from.name if feedInfo.to.count > 0 { feedDetail += "\nTo: " for to in feedInfo.to { feedDetail += to.name + "\n" } } if let date = feedInfo.createdTime { feedDetail += "\nDate: " + NSDate().convertFacebookTime(date) } if let story = feedInfo.story { feedDetail += "\nStory: " + story } if let message = feedInfo.message { feedDetail += "\nMessage: " + message } if let name = feedInfo.name { feedDetail += "\nName: " + name } if let type = feedInfo.type { feedDetail += "\nType: " + type } if let updatedTime = feedInfo.updatedTime { feedDetail += "\nupdatedTime: " + NSDate().convertFacebookTime(updatedTime) } feedTextView.text = feedDetail self.navigationController?.navigationBar.tintColor = UIColor.whiteColor() }
ããã ãã§ãã Githubã®ãããžã§ã¯ãã®ãœãŒã¹ã³ãŒãã¯ããã®ãªã³ã¯ããããŠã³ããŒãã§ããŸãã ç§ã®èšäºãæ°ã«å ¥ã£ãããç§ã¯