ReactiveCocoa用法示例(二)

知識點

  • RACSignal與OC對象方法的綁定
  • RACSignal與OC對象屬性的綁定
  • [RACSignal merge:], Merge操作的使用
  • [RACSignal combineLatest: reduce:^id(){}], Combine和reduce操作的使用
  • [RACSignal return:nil] 與[RACSignal empty], nil信號和空信號的區別

示例項目功能

  • 點擊電子發票 紙質發票 個人 公司四個按鈕 界面作出變化
  • 根據四種狀態檢查Textfiled內容是否輸入錯誤
  • 輸入錯誤Toast彈出錯誤內容
  • 輸入正確Alert彈出確認信息

示例項目地址

github: https://github.com/heeween/RACComand2.git
用到的城市選擇器屬于Jonhory

2017-11-14 13.58.33.gif

構建電子發票 紙質發票 個人 公司四個按鈕的RACCommand對象

// GBInvoiceContentViewModel.m中代碼
    self.personalCmd = [[RACCommand alloc] initWithSignalBlock:^RACSignal *(id input) {
        return [RACSignal return:@(Invoice_Owner_Personal)];
    }];
    self.companyCmd = [[RACCommand alloc] initWithSignalBlock:^RACSignal *(id input) {
        return [RACSignal return:@(Invoice_Owner_Company)];
    }];
    self.nonPaperCmd = [[RACCommand alloc] initWithSignalBlock:^RACSignal *(id input) {
        return [RACSignal return:@(Invoice_Source_NonPaper)];
    }];
    self.paperCmd = [[RACCommand alloc] initWithSignalBlock:^RACSignal *(id input) {
        return [RACSignal return:@(Invoice_Source_Paper)];
    }];

在控制器中將RACCommand對象和按鈕事件綁定

// GBInvoiceContentController.m 中代碼
    self.contentView.ownerItemView.personalButton.rac_command = self.viewModel.personalCmd;
    self.contentView.ownerItemView.companyButton.rac_command = self.viewModel.companyCmd;
    self.sourceView.nonPaperButton.rac_command = self.viewModel.nonPaperCmd;
    self.sourceView.paperButton.rac_command = self.viewModel.paperCmd;

將上述四個command的包含的信號合并為source何owner兩個信號

// GBInvoiceContentViewModel.m中代碼
    RACSignal *personalSignal = [[self.personalCmd executionSignals] switchToLatest];
    RACSignal *companySignal = [[self.companyCmd executionSignals] switchToLatest];
    RACSignal *nonPaperSignal = [[self.nonPaperCmd executionSignals] switchToLatest];
    RACSignal *paperSignal = [[self.paperCmd executionSignals] switchToLatest];
    self.ownerSignal = [RACSignal merge:@[personalSignal,companySignal]];
    self.sourceSignal = [RACSignal merge:@[nonPaperSignal,paperSignal]];

分別將source和owner信號綁定對應的view上

// GBInvoiceContentController.m 中代碼
    RAC(self.sourceView,type) = self.viewModel.sourceSignal;
    RAC(self.confirmView,type) = self.viewModel.sourceSignal;
    RAC(self.addressView,type) = self.viewModel.sourceSignal;
    RAC(self.contentView,type) = self.viewModel.ownerSignal;

將source和owner兩個信號合并為動畫信號

// GBInvoiceContentViewModel.m中代碼
    self.animateSignal = [RACSignal merge:@[self.ownerSignal,self.sourceSignal]];

將動畫信號綁定到控制器的動畫方法

// GBInvoiceContentController.m 中代碼
    [self rac_liftSelector:@selector(animatieUpdateSubview:) withSignals:self.viewModel.animateSignal, nil];

/** 動畫子控件 */
- (void)animatieUpdateSubview:(id)obj {
    [UIView animateWithDuration:0.3 animations:^{
        [self.view layoutIfNeeded];
    }];
}

收集控制器中各輸入框的信號,并且綁定到viewmode中發票對象的屬性上

// GBInvoiceContentController.m 中代碼
        self.viewModel.companyNameSignal = self.contentView.companyNameItemView.inputField.rac_textSignal;
        self.viewModel.taxNumberSignal = self.contentView.taxPayerIDItemView.inputField.rac_textSignal;
        self.viewModel.remarkSignal = self.contentView.markField.rac_textSignal;
        self.viewModel.receiverSignal = self.addressView.contactNameItemView.inputField.rac_textSignal;
        self.viewModel.phoneSignal = self.addressView.contactPhoneItemView.inputField.rac_textSignal;
        self.viewModel.regionSignal = self.addressView.contactAddressItemView.inputField.rac_textSignal;
        self.viewModel.minAddressSignal = self.addressView.contactAddressDescItemView.inputField.rac_textSignal;
        self.viewModel.emailSignal = self.addressView.contactMailItemView.inputField.rac_textSignal;
        RAC(self.viewModel.invoice,companyName) = self.viewModel.companyNameSignal;
        RAC(self.viewModel.invoice,taxNumber) = self.viewModel.taxNumberSignal;
        RAC(self.viewModel.invoice,receiver) = self.viewModel.receiverSignal;
        RAC(self.viewModel.invoice,region) = self.viewModel.regionSignal;
        RAC(self.viewModel.invoice,minAddress) = self.viewModel.minAddressSignal;
        RAC(self.viewModel.invoice,email) = self.viewModel.emailSignal;

在viewmodel中根據source和owner信號,對各個輸入框的信號進行merge和reduce操作,整合成錯誤字符串信號errorStringSignal

// GBInvoiceContentViewModel.m中代碼
- (RACSignal *)errorStringSignal {
    if (!_errorStringSignal) {
        RACSignal *companyNameError = [self.companyNameSignal map:^id(NSString *value) {
            if (value.length <= 0) {
                return @"公司名稱不能為空";
            }else {
                return @"";
            }
        }];
        RACSignal *taxNumberError = [self.taxNumberSignal map:^id(NSString *value) {
            if (value.length <= 0) {
                return @"納稅人識別號不能為空";
            }else if (![value isTaxNumber]) {
                return @"納稅人識別號輸入不正確";
            }else {
                return @"";
            }
        }];
        RACSignal *receiverError = [self.receiverSignal map:^id(NSString *value) {
            return value.length > 0 ? @"" : @"聯系人不能為空";
        }];
        RACSignal *phoneError = [self.phoneSignal map:^id(NSString *value) {
            if (value.length <= 0) {
                return @"電話不能為空";
            }else if (value.length != 11) {
                return @"電話輸入不正確";
            }else {
                return @"";
            }
        }];
        RACSignal *regionError = [self.regionSignal map:^id(NSString *value) {
            return value.length > 0 ? @"" : @"地址不能為空";
        }];
        RACSignal *minaddressError = [self.minAddressSignal map:^id(NSString *value) {
            return value.length > 0 ? @"" : @"詳細地址不能為空";
        }];
        RACSignal *emailError = [self.emailSignal map:^id(NSString *value) {
            if (value.length <= 0) {
                return @"郵箱不能為空";
            }else if (![value isEmail]) {
                return @"郵箱輸入不正確";
            }else {
                return @"";
            }
        }];
        
        
        NSDictionary *sourceDict =
        @{
          @(Invoice_Source_Paper):
              [RACSignal combineLatest:@[receiverError,phoneError,regionError,minaddressError] reduce:^id(NSString *receiver, NSString *phone,NSString *region,NSString *minaddress){
                  NSMutableArray *array = [NSMutableArray array];
                  if (receiver.length > 0) { [array addObject:receiver]; }
                  if (phone.length > 0) { [array addObject:phone]; }
                  if (region.length > 0) { [array addObject:region]; }
                  if (minaddress.length > 0) { [array addObject:minaddress]; }
                  return array;
              }],
          @(Invoice_Source_NonPaper):
              [RACSignal combineLatest:@[receiverError,phoneError,emailError] reduce:^id(NSString *receiver, NSString *phone,NSString *email){
                  NSMutableArray *array = [NSMutableArray array];
                  if (receiver.length > 0) { [array addObject:receiver]; }
                  if (phone.length > 0) { [array addObject:phone]; }
                  if (email.length > 0) { [array addObject:email]; }
                  return array;
              }]
          };
        RACSignal *sourceError = [RACSignal switch:self.sourceSignal cases:sourceDict default:nil];
        NSDictionary *ownerDict =
        @{
          @(Invoice_Owner_Personal): [RACSignal return:nil],
          @(Invoice_Owner_Company):
              [RACSignal combineLatest:@[companyNameError,taxNumberError] reduce:^id(NSString *companyName, NSString *taxNumber){
                  NSMutableArray *array = [NSMutableArray array];
                  if (companyName.length > 0) { [array addObject:companyName]; }
                  if (taxNumber.length > 0) { [array addObject:taxNumber]; }
                  return array;
              }]
          };
        RACSignal *ownerError = [RACSignal switch:self.ownerSignal cases:ownerDict default:nil];
        _errorStringSignal = [RACSignal combineLatest:@[sourceError,ownerError] reduce:^id(NSArray *sourceArray, NSArray *ownerArray){
            NSMutableArray *array = [NSMutableArray array];
            [array addObjectsFromArray:sourceArray];
            [array addObjectsFromArray:ownerArray];
            return array;
        }];
    }
    return _errorStringSignal;
}

把errorStringSignal和errorstring屬性

  • 這步看似多次一舉,實則是因為頁面需求并不是每次有錯誤就彈出,而是在點擊下一步的時候才彈出
  • 因為先用errorstring屬性保存錯誤值,點擊下一步的通過subscriper發出
// GBInvoiceContentController.m 中代碼
        RAC(self.viewModel,errorStrings) = self.viewModel.errorStringSignal;

創建下一步command,有錯誤senderror,沒有sendcomplete

// GBInvoiceContentViewModel.m中代碼
- (RACCommand *)nextCmd {
    if (!_nextCmd) {
        @weakify(self);
        _nextCmd = [[RACCommand alloc] initWithSignalBlock:^RACSignal *(id input) {
            return [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
                @strongify(self);
                if (self.errorStrings == nil || self.errorStrings.count <= 0) {
                    [subscriber sendNext:[self.invoice showParame]];
                    [subscriber sendCompleted];
                }else {
                    [subscriber sendError:[NSError errorWithDomain:@"輸入有誤" code:0 userInfo:@{@"errorStrings":self.errorStrings}]];
                }
                return nil;
            }];
        }];
    }
    return _nextCmd;
}

下一步command與下一步按鈕綁定,并且綁定成功和失敗信號到對應的控制器方法

// GBInvoiceContentController.m 中代碼
        self.confirmView.confirmButton.rac_command = self.viewModel.nextCmd;

        [self rac_liftSelector:@selector(showAlert:) withSignals:[[self.viewModel.nextCmd executionSignals] switchToLatest], nil];
        [self rac_liftSelector:@selector(showError:) withSignals:self.viewModel.nextCmd.errors, nil];

/** 輸入錯誤提醒 */
- (void)showError:(NSError *)error {
    NSArray *errorStrings = error.userInfo[@"errorStrings"];
    [CSToastManager setDefaultPosition:CSToastPositionCenter];
    [self.view makeToast:[errorStrings componentsJoinedByString:@"  |  "]];
}
/** 輸入成功彈窗 */
- (void)showAlert:(id)obj {
    GBInvoiceAlert *alertView = [GBInvoiceAlert showWith:obj confirmBlock:nil];
    alertView.confirmBtn.rac_command = self.viewModel.confirmCmd;
}

創建comfirmCmd

// GBInvoiceContentViewModel.m中代碼
- (RACCommand *)confirmCmd {
    if (!_confirmCmd) {
        _confirmCmd = [[RACCommand alloc] initWithSignalBlock:^RACSignal *(id input) {
            return [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
                if (arc4random() % 2 == 0) {
                    [subscriber sendNext:@"開票成功"];
                    [subscriber sendCompleted];
                }else {
                    NSError *error = [NSError errorWithDomain:@"開票失敗" code:00 userInfo:nil];
                    [subscriber sendError:error];
                }
                return nil;
            }];
        }];
    }
    return _confirmCmd;
}

最后一步綁定彈窗確定按鈕和comfirmCmd,并且綁定成功和失敗信號到對應的控制器方法

// GBInvoiceContentController.m 中代碼
    // 綁定彈窗的成功和失敗信號
    {
        [self rac_liftSelector:@selector(postParamSuccess:) withSignals:[[self.viewModel.confirmCmd executionSignals] switchToLatest], nil];
        [self rac_liftSelector:@selector(postParamFailure:) withSignals:self.viewModel.confirmCmd.errors, nil];
    }

/** 開票成功 */
- (void)postParamSuccess:(id)obj {
    [self.view makeToast:@"開票成功"];
}
/** 開票失敗 */
- (void)postParamFailure:(id)obj {
    [self.view makeToast:@"開票失敗"];
}

1,使用RACComand和RACSigna,可以很方便的拿到各自獨立的事件或信號.
2, RAC有大量的信號操作,可以非常方便做業務邏輯.不管是信號合并信號轉化都不需要蛋疼的中間變量.
3,RAC有吊炸天的綁定,更是方便了從結果到界面的one step.

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,606評論 6 533
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,582評論 3 418
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,540評論 0 376
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,028評論 1 314
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,801評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,223評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,294評論 3 442
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,442評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,976評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,800評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,996評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,543評論 5 360
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,233評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,662評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,926評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,702評論 3 392
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,991評論 2 374

推薦閱讀更多精彩內容