ReactiveCocoa用法示例(二)

知識點(diǎn)

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

示例項目功能

  • 點(diǎn)擊電子發(fā)票 紙質(zhì)發(fā)票 個人 公司四個按鈕 界面作出變化
  • 根據(jù)四種狀態(tài)檢查Textfiled內(nèi)容是否輸入錯誤
  • 輸入錯誤Toast彈出錯誤內(nèi)容
  • 輸入正確Alert彈出確認(rèn)信息

示例項目地址

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

2017-11-14 13.58.33.gif

構(gòu)建電子發(fā)票 紙質(zhì)發(fā)票 個人 公司四個按鈕的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信號綁定對應(yīng)的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中發(fā)票對象的屬性上

// 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中根據(jù)source和owner信號,對各個輸入框的信號進(jìn)行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 ? @"" : @"聯(lián)系人不能為空";
        }];
        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 ? @"" : @"詳細(xì)地址不能為空";
        }];
        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屬性

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

創(chuàng)建下一步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與下一步按鈕綁定,并且綁定成功和失敗信號到對應(yīng)的控制器方法

// 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;
}

創(chuàng)建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,并且綁定成功和失敗信號到對應(yīng)的控制器方法

// 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,可以很方便的拿到各自獨(dú)立的事件或信號.
2, RAC有大量的信號操作,可以非常方便做業(yè)務(wù)邏輯.不管是信號合并信號轉(zhuǎn)化都不需要蛋疼的中間變量.
3,RAC有吊炸天的綁定,更是方便了從結(jié)果到界面的one step.

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市伪冰,隨后出現(xiàn)的幾起案子拄踪,更是在濱河造成了極大的恐慌,老刑警劉巖作彤,帶你破解...
    沈念sama閱讀 218,858評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡鉴分,警方通過查閱死者的電腦和手機(jī)劣砍,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,372評論 3 395
  • 文/潘曉璐 我一進(jìn)店門惧蛹,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事香嗓⊙盖唬” “怎么了?”我有些...
    開封第一講書人閱讀 165,282評論 0 356
  • 文/不壞的土叔 我叫張陵靠娱,是天一觀的道長沧烈。 經(jīng)常有香客問我,道長像云,這世上最難降的妖魔是什么锌雀? 我笑而不...
    開封第一講書人閱讀 58,842評論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮迅诬,結(jié)果婚禮上腋逆,老公的妹妹穿的比我還像新娘。我一直安慰自己侈贷,他們只是感情好惩歉,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,857評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著俏蛮,像睡著了一般柬泽。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上嫁蛇,一...
    開封第一講書人閱讀 51,679評論 1 305
  • 那天锨并,我揣著相機(jī)與錄音,去河邊找鬼睬棚。 笑死第煮,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的抑党。 我是一名探鬼主播包警,決...
    沈念sama閱讀 40,406評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼底靠!你這毒婦竟也來了害晦?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,311評論 0 276
  • 序言:老撾萬榮一對情侶失蹤暑中,失蹤者是張志新(化名)和其女友劉穎壹瘟,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體鳄逾,經(jīng)...
    沈念sama閱讀 45,767評論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡稻轨,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,945評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了雕凹。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片殴俱。...
    茶點(diǎn)故事閱讀 40,090評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡政冻,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出线欲,到底是詐尸還是另有隱情明场,我是刑警寧澤,帶...
    沈念sama閱讀 35,785評論 5 346
  • 正文 年R本政府宣布李丰,位于F島的核電站榕堰,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏嫌套。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,420評論 3 331
  • 文/蒙蒙 一圾旨、第九天 我趴在偏房一處隱蔽的房頂上張望踱讨。 院中可真熱鬧,春花似錦砍的、人聲如沸痹筛。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,988評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽帚稠。三九已至,卻和暖如春床佳,著一層夾襖步出監(jiān)牢的瞬間滋早,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,101評論 1 271
  • 我被黑心中介騙來泰國打工砌们, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留杆麸,地道東北人。 一個月前我還...
    沈念sama閱讀 48,298評論 3 372
  • 正文 我出身青樓浪感,卻偏偏與公主長得像昔头,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子影兽,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,033評論 2 355

推薦閱讀更多精彩內(nèi)容