ReactiveCocoa代碼實踐之-UI組件的RAC信號操作


上一節是自己對網絡層的一些重構,本節是自己一些代碼小實踐做出的一些demo程序,基本涵蓋大多數UI控件操作。

 

一.用UISlider實現調色板

假設我們現在做一個demo,上面有一個View用來展示顏色,下面有三個UISlider滑竿分別控制RGB的色值,隨着不同滑竿的拖動上面view的顏色會隨之改變。 可以先腦補一下不用RAC該怎么寫。 如果使用RAC只需要將三個信號包裝起來用適當的操作就能實現。

// 拖線的UI控件
@property (weak, nonatomic) IBOutlet UIView *topView;
@property (weak, nonatomic) IBOutlet UISlider *slider1;
@property (weak, nonatomic) IBOutlet UISlider *slider2;
@property (weak, nonatomic) IBOutlet UISlider *slider3;
  
// viewDidLoad中
// 分別將三個控件的改變都包成一個信號。
RACSignal *s1 = [[self.slider1 rac_newValueChannelWithNilValue:@0]startWith:@0];
RACSignal *s2 = [[self.slider2 rac_newValueChannelWithNilValue:@0]startWith:@0];
RACSignal *s3 = [[self.slider3 rac_newValueChannelWithNilValue:@0]startWith:@0];
 
RACSignal *threeSignal = [RACSignal combineLatest:@[s1,s2,s3] reduce:^id(NSNumber* value1,NSNumber* value2,NSNumber* value3){
    return @[value1,value2,value3];
}];
 
// 監聽這個"合成"后的信號,改變view的顏色
[threeSignal subscribeNext:^(NSArray *arr) {
    self.topView.backgroundColor = [UIColor colorWithRed:[arr[0] doubleValue] green:[arr[1] doubleValue] blue:[arr[2] doubleValue] alpha:1];
}];

上面的startWith:@0需要注意,如果不加這個初始值那必須在三個滑竿都動一下才能顯示顏色。  上面使用的方法時UISlider專屬的,也可以用下面的方法寫,這個是UIControl的方法會支持更多其他UI控件。

RACSignal *s1 = [[[self.slider1 rac_signalForControlEvents:UIControlEventValueChanged] map:^id(id value) {
    return @(self.slider1.value);
}] startWith:@0];  



二.簡潔代碼實現登錄邏輯

在UI控件中難點不多,但是值得注意的就是各種狀態的多級管理,如果哪里疏忽了就很容易造成bug,這也就導致很多地方有判斷結構,並且各種來回賦值。 假設現在需要做一個登錄框,有賬號密碼和同意條款三項,必須滿足賬號密碼大於2位且選擇了同意,才允許注冊。 舊的寫法非常麻煩,還需要監聽valueChange事件等。如果用RAC只需要寫如下代碼:

@property (weak, nonatomic) IBOutlet UITextField *accountTxt;
@property (weak, nonatomic) IBOutlet UITextField *pwdTxt;
@property (weak, nonatomic) IBOutlet SXSwitch *agreeSw; // 同意條款
@property (weak, nonatomic) IBOutlet UIButton *loginBtn; // 注冊按鈕
  
// viewDidLoad方法
self.loginBtn.enabled = NO;
RAC(self.loginBtn , enabled) = [RACSignal combineLatest:@[self.accountTxt.rac_textSignal , self.pwdTxt.rac_textSignal, self.agreeSw.rac_newOnChannel] reduce:^(NSString *account, NSString *pwd, NSNumber *isOn){
    return @((account.length > 2)&&(pwd.length >2)&&[isOn boolValue]);
}];

這其中combineLatest數組中用的都是控件專屬的信號, 也可以使用RAC(self.agreeSw, on) 這種寫法直接把某一個屬性的狀態用信號傳過來。但是這里需要注意:假設你監聽了A類的B屬性時,只有走了B屬性的set方法才會被監聽捕獲,如果是通過其他方法修改的屬性值則無效。 比如UISwitch的來回撥動過程中並沒有走on這個屬性的set方法。

 

三.通過interval方法實現時鍾

這是一種默認循環的方法,除非你通過控制Disposable把他禁了。 interval這個方法就是傳入一個參數是間隔時間,然后內部每隔這一段時間就發一個[NSDate date]的對象,然后block內部把這個date設置一個格式以字符串的方法返回。

RAC(self, timeLabel.text) = [[[RACSignal interval:1 onScheduler:[RACScheduler currentScheduler]] startWith:[NSDate date]] map:^id (NSDate *value) {
        NSDateComponents *dateComponents = [[NSCalendar currentCalendar] components:NSCalendarUnitHour | NSCalendarUnitMinute | NSCalendarUnitSecond fromDate:value];
        return [NSString stringWithFormat:@"%02ld:%02ld:%02ld", (long)dateComponents.hour, (long)dateComponents.minute, (long)dateComponents.second];
    }];

四.其他控件事件操作

除了上面的UIButton,UISlider,UIControl的分類方法還有很多操作

UISegmentedControl (RACSignalSupport)分類就為此控件提供了便捷處理方法,相比於常規的監聽seg的元素點擊事件,再取出當前選中的index。RACSignal可以直接得到需要的值

     [[self.seg rac_newSelectedSegmentIndexChannelWithNilValue:@0]subscribeNext:^(id x) {
        // 返回的基本數據類型都被裝包成NSNumber,可在此做一些判斷操作
        NSLog(@"selectIndex-%@",x);
    }];

UIDatePicker (RACSignalSupport)分類為時間選擇框封裝了一個操作,每當選框改變時返回NSDate類型

[[picker rac_newDateChannelWithNilValue:[NSDate date]]subscribeNext:^(NSDate *x) {
    NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
    dateFormatter.dateFormat = @"HH:mm";
    dateFormatter.locale = [[NSLocale alloc] initWithLocaleIdentifier:@"zh_CN"];
    NSString *dateStr = [dateFormatter stringFromDate:x];
}];

如果在這里給控件賦值,每一次改動都會讓展示控件的值更新,如果有的設計不希望這么頻繁只有在點擊確認后再將時間顯示可以根據自己喜好自行賦值。

除此這些還有很多UI控件綁定的方法 UIAlertView (RACSignalSupport)  里面就提供了一些方法比如點擊彈窗按鈕可以在subscribeNext里統一處理各個按鈕的點擊事件。 但是現在UIAlertView已被UIAlertController取代所以,UIAlertView和UIActionSheet這里可以忽略不提。

 

五.生命周期相關操作

UITableView和UICollectionView的Cell都有重用的機制,如果給這個Cell綁定了一些監聽,那這個Cell被重用它子控件的監聽該何去何從?UITableViewCell (RACSignalSupport)、UICollectionReusableView (RACSignalSupport)這兩個分類里提供了即將重用時的信號rac_prepareForReuseSignal

做過兩個類似的場景,一個是tableView的cell回復按鈕點擊會跳到回復頁,一個是collection的item內有個按鈕點擊就變顏色。

// UITableViewDataSource
- (UITableViewCell *)tableView:(nonnull UITableView *)tableView cellForRowAtIndexPath:(nonnull NSIndexPath *)indexPath {
    SXFeedbackCell * cell = [tableView dequeueReusableCellWithIdentifier:@"SXFeedbackCell"];
    @weakify(self)
    [[[cell.replyButton rac_signalForControlEvents:UIControlEventTouchUpInside] takeUntil:cell.rac_prepareForReuseSignal]
         subscribeNext:^(UIButton *x) {
             @strongify(self)
             // 處理一些其他邏輯
             [self.navigationController pushViewController:[SXReplyPage new] animated:YES];
        }];
    return cell;
}
  
// UICollectionViewDataSource
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
    UICollectionViewCell * cell = [collectionView dequeueReusableCellWithIdentifier:@"SXDownloadCell"];
    [[[cell.changeBtn rac_signalForControlEvents:UIControlEventTouchUpInside] takeUntil:cell.rac_prepareForReuseSignal]
     subscribeNext:^(UIButton *x) {
        cell.backgroundColor = [UIColor grayColor];
        }];
    return cell;
}

其中takeUntil操作是監聽某個事件直到什么時候結束。當這個cell即將重用時rac_prepareForReuseSignal到來會觸發disposable信號結束監聽。

非重用類型的控件的生命周期可以用rac_willDeallocSignal 信號監聽,但是在開發中很少會用到此信號,因為大多是信號操作的內部代碼里都幫你做了這個操作,即監聽一個事件直到自己結束時停止監聽。

// rac_textSignal源碼
- (RACSignal *)rac_textSignal {
    @weakify(self);
    return [[[[[RACSignal
        defer:^{
            @strongify(self);
            return [RACSignal return:self];
        }]
        concat:[self rac_signalForControlEvents:UIControlEventAllEditingEvents]]
        map:^(UITextField *x) {
            return x.text;
        }]
        takeUntil:self.rac_willDeallocSignal]
        setNameWithFormat:@"%@ -rac_textSignal", self.rac_description];
}
 
// rac_signalForControlEvents源碼
- (RACSignal *)rac_signalForControlEvents:(UIControlEvents)controlEvents {
    @weakify(self);
    return [[RACSignal
        createSignal:^(id<RACSubscriber> subscriber) {
            @strongify(self);
            [self addTarget:subscriber action:@selector(sendNext:) forControlEvents:controlEvents];
            [self.rac_deallocDisposable addDisposable:[RACDisposable disposableWithBlock:^{
                [subscriber sendCompleted];
            }]];
            return [RACDisposable disposableWithBlock:^{
                @strongify(self);
                [self removeTarget:subscriber action:@selector(sendNext:) forControlEvents:controlEvents];
            }];
        }]
        setNameWithFormat:@"%@ -rac_signalForControlEvents: %lx", self.rac_description, (unsigned long)controlEvents];
}

有不同見解的地方歡迎吐槽。 本文禁止轉載

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM