1 // 2 // RootViewController.m 3 // SimpleWeather 4 // 5 // Created by TBXark on 15-4-17. 6 // Copyright (c) 2015年 Ryan Nystrom. All rights reserved. 7 // 8 9 #import "RootViewController.h" 10 #import <ReactiveCocoa.h> 11 #import <TSMessage.h> 12 #import "ViewModel.h" 13 14 @interface RootViewController ()<UIGestureRecognizerDelegate> 15 16 @property (nonatomic,strong) UITextField *textfield; 17 @property (nonatomic,strong) RACSubject *gestureRecognizerIsRunningSubject; 18 @property (nonatomic,strong) RACSubject *gestureRecognizerValueSubject; 19 @property (nonatomic,strong) UIView *someView; 20 21 @property (nonatomic,strong) ViewModel *viewModel; 22 @property(assign) NSUInteger scoreUpdates; 23 24 25 @end 26 27 static NSUInteger const kMaxUploads = 5; 28 29 30 @implementation RootViewController 31 32 - (void)viewDidLoad { 33 [super viewDidLoad]; 34 35 36 // 模式 37 // 38 // 在ReactiveCocoa中有三种基本的模式:责任链、分割和组合模式(chaining, splitting, and combining)在ReactiveCocoa中的核心是signal:(信号),它表示不断变化的状态。当我们使用chain、split和combine时,实际上我们就是在操作这些signal 39 40 //*************************************************************************// 41 42 // 责任链:chaining 43 // Chaining是在ReactiveCocoa最常用的模式:将一个已有的signal转换为一个新的signal。常用的操作是创建一个新的signal,再对它使用filter:、map:或startWith:等方法。 44 45 CGFloat width = self.view.bounds.size.width; 46 _textfield = [[UITextField alloc] initWithFrame:CGRectMake(40,30, width-80, 30)]; 47 _textfield.textAlignment = NSTextAlignmentCenter; 48 // [self.view addSubview:_textfield]; 49 50 RAC(self,textfield.text) = [[[RACSignal interval:1 onScheduler:[RACScheduler mainThreadScheduler]] startWith:[NSDate date]] map:^id(NSDate *value) { 51 NSDateFormatter *df = [[NSDateFormatter alloc] init]; 52 df.dateFormat = @"yyyy-MM-dd hh:mm:ss"; 53 return [df stringFromDate:value]; 54 }]; 55 56 //*************************************************************************// 57 58 // 分割:Splitting 59 // Splitting与chaining比较类似,也是将signal转换为其它的sginal,不同之处在于,Splitting会重复使用中间的signals。Splitting看起来要复杂些,其实也就是一个signals使用多次罢了。 60 61 UITextField *dateTextField = [[UITextField alloc] initWithFrame:CGRectMake(40, 70, width-80, 30)]; 62 UITextField *timeTextField = [[UITextField alloc] initWithFrame:CGRectMake(40, 100, width-80, 30)]; 63 // [self.view addSubview:dateTextField]; 64 // [self.view addSubview:timeTextField]; 65 dateTextField.textAlignment = NSTextAlignmentCenter; 66 timeTextField.textAlignment = NSTextAlignmentCenter; 67 68 69 RACSignal *mainSingal = [[[RACSignal interval:1 onScheduler:[RACScheduler mainThreadScheduler]] startWith:[NSDate date]] map:^id(NSDate *value) { 70 NSDateComponents *dateComponents = [[NSCalendar currentCalendar] components:NSMinuteCalendarUnit | NSSecondCalendarUnit fromDate:value]; 71 return dateComponents; 72 }]; 73 74 // deliverOn:[RACScheduler mainThreadScheduler]]; 75 RAC(dateTextField,text) = [[mainSingal map:^id(NSDateComponents *value) { 76 return [NSString stringWithFormat:@"%u-%u-%u",value.year,value.month,value.day]; 77 }] deliverOn:[RACScheduler mainThreadScheduler]]; 78 RAC(timeTextField,text) = [[mainSingal map:^id(NSDateComponents *value) { 79 return [NSString stringWithFormat:@"%u:%u:%u",value.hour,value.minute,value.second]; 80 }] deliverOn:[RACScheduler mainThreadScheduler]]; 81 82 //*************************************************************************// 83 84 // 组合:combining 85 // combining就是将几个signal结合起来创建出一个新的signal。 86 // 组合的信号(combined signal)只会在所有的输入至少都有一个值的时候才会发送它的第一个值 87 UITextField *userName = [[UITextField alloc] initWithFrame:CGRectMake(40, 130, width-80, 30)]; 88 UITextField *password = [[UITextField alloc] initWithFrame:CGRectMake(40, 165, width-80, 30)]; 89 UIButton *loginButton = [[UIButton alloc] initWithFrame:CGRectMake(40, 200, width-80, 30)]; 90 userName.backgroundColor = [UIColor grayColor]; 91 password.backgroundColor = [UIColor grayColor]; 92 loginButton.backgroundColor = [UIColor darkGrayColor]; 93 [loginButton setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal]; 94 [loginButton setTitle:@"Login" forState:UIControlStateNormal]; 95 [loginButton setTitle:@"Waiting" forState:UIControlStateDisabled]; 96 // [self.view addSubview:userName]; 97 // [self.view addSubview:password]; 98 // [self.view addSubview:loginButton]; 99 100 // 在这里,我们将“登录”按钮的enable状态绑定到使用combineLatest:reduce:方法创建的signal上。 101 // 这个方法的第二个参数是一个block,这个block的参数是combineLatest中的参数的最新值的组合。我们将两个文本框的text signal一起传到combineLatest,在reduce的block中,该block也就会接收到两个NSString的参数. 102 // 这个block的工作就是将两个参数值组合起来生成一个值,然后返回。 103 RAC(loginButton,enabled) = [RACSignal combineLatest:@[userName.rac_textSignal,password.rac_textSignal] reduce:^id(NSString *userName, NSString *password) { 104 return @(userName.length >= 6 && password.length >= 6); 105 }]; 106 107 //*************************************************************************// 108 109 //RACSubjects 110 // 111 // RACSubjects则充当了非reactive和 reactive代码的桥梁。 112 // RACSubject是能够手动发送新值的signal。 113 // 虽然RACSubjects是非reactive代码与ReactiveCocoa代码的桥梁,但过分滥用也是有风险的。当我们能够通过chaining signals完成任务的话,就不要依赖于RACSubjects的值。 114 // 虽然subscriptions很有用,执行副作用也是必要的,但要小心过度使用。它们就是可变的变量、状态,这些正是ReactiveCocoa所避免的。在能够通过绑定属性映射signals完成任务的时候,就不要使用RACSubjects。 115 116 self.gestureRecognizerIsRunningSubject = [RACSubject subject]; 117 self.gestureRecognizerValueSubject = [RACSubject subject]; 118 self.someView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 40, 40)]; 119 self.someView.layer.cornerRadius = 20; 120 self.someView.backgroundColor = [UIColor darkGrayColor]; 121 122 UIPanGestureRecognizer *tap = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(gestureRecognizerReceivedTouch:)]; 123 tap.delegate = self; 124 RAC(self,someView.frame) = [self.gestureRecognizerValueSubject map:^id(NSValue *value) { 125 CGPoint location = [value CGPointValue]; 126 CGFloat size = 40.0f; 127 return [NSValue valueWithCGRect:CGRectMake(location.x - size/2.0f, location.y - size/2.0f, size, size)]; 128 }]; 129 130 // [self.view addSubview:self.someView]; 131 // [self.view addGestureRecognizer:tap]; 132 133 134 //*************************************************************************// 135 136 //RACCommand 137 // 138 // RACCommand类用于表示事件的执行,一般来说是在UI上的某些动作来触发这些事件,比如点击一个按钮。RACCommand的实例能够决定是否可以被执行,这个特性能反应在UI上,而且它能确保在其不可用时不会被执行。通常,当一个命令可以执行时,会将它的属性allowsConcurrentExecution设置为它的默认值:NO,从而确保在这个命令已经正在执行的时候,不会同时再执行新的操作。命令执行的返回值是一个RACSignal,因此我们能对该返回值进行next:,completed或error: 139 140 141 // RAC给UITableViewCell提供了一个方法:rac_prepareForReuseSignal,它的作用是当Cell即将要被重用时,告诉Cell。想象Cell上有多个button,Cell在初始化时给每个button都addTarget:action:forControlEvents,被重用时需要先移除这些target,下面这段代码就可以很方便地解决这个问题: 142 // [[[self.cancelButton 143 // rac_signalForControlEvents:UIControlEventTouchUpInside] 144 // takeUntil:self.rac_prepareForReuseSignal] 145 // subscribeNext:^(UIButton *x) { 146 // // do other things 147 // }]; 148 149 150 151 //*************************************************************************// 152 153 //概念 154 155 //Streams 抽象基类 156 // -Signals 信号 157 // -Sequences 信号集合 158 159 //RACCommand 命令? 160 //Schedulers 多线程? 161 162 //Subscription 订阅 接收 -subscribeNext: -subscribeError: -subscribeCompleted: 163 //Injecting effects 注入效果 -doNext: -doError: -doCompleted: 164 //Mapping 信号转换 165 //Filtering 信号过滤 166 //Concatenating 信号拼接 167 //Flattening 数组拼接 168 //Mapping and flattening 先Map在Flatten 169 170 [self reactiveCocoaBasicOperation]; 171 [self reactiveCocoaMVVM]; 172 173 } 174 175 - (void)reactiveCocoaMVVM 176 { 177 CGFloat width = self.view.bounds.size.width; 178 UITextField *nameField = [[UITextField alloc] initWithFrame:CGRectMake(40, 40, width-80, 40)]; 179 [self.view addSubview:nameField]; 180 181 UILabel *scoreField = [[UILabel alloc] initWithFrame:CGRectMake(40, 100, width-80, 40)]; 182 [self.view addSubview:scoreField]; 183 184 UIStepper *scoreStepper = [[UIStepper alloc] initWithFrame:CGRectMake(100, 160, width-200, 40)]; 185 [self.view addSubview:scoreStepper]; 186 187 UIButton *uploadButton = [[UIButton alloc] initWithFrame:CGRectMake(100, 210, width-200, 40)]; 188 [self.view addSubview:uploadButton]; 189 [uploadButton setTitle:@"Upload" forState:UIControlStateNormal]; 190 uploadButton.backgroundColor = [UIColor darkGrayColor]; 191 192 193 194 self.viewModel = [ViewModel new]; 195 196 //using with @strongify(self) this makes sure that self isn‘t retained in the blocks 197 //this is declared in RACEXTScope.h 198 @weakify(self); 199 200 //Start Binding our properties 201 RAC(nameField,text) = [RACObserve(self.viewModel, playerName) distinctUntilChanged]; 202 203 [[nameField.rac_textSignal distinctUntilChanged] subscribeNext:^(NSString *x) { 204 //this creates a reference to self that when used with @weakify(self); 205 //makes sure self isn‘t retained 206 @strongify(self); 207 self.viewModel.playerName = x; 208 }]; 209 210 //the score property is a double, RC gives us updates as NSNumber which we just call 211 //stringValue on and bind that to the scorefield text 212 RAC(scoreField,text) = [RACObserve(self.viewModel,points) map:^id(NSNumber *value) { 213 return [value stringValue]; 214 }]; 215 216 //Setup bind the steppers values 217 scoreStepper.value = self.viewModel.points; 218 RAC(scoreStepper,stepValue) = RACObserve(self.viewModel,stepAmount); 219 RAC(scoreStepper,maximumValue) = RACObserve(self.viewModel,maxPoints); 220 RAC(scoreStepper,minimumValue) = RACObserve(self.viewModel,minPoints); 221 //bind the hidden field to a signal keeping track if 222 //we‘ve updated less than a certain number times as the view model specifies 223 RAC(scoreStepper,hidden) = [RACObserve(self,scoreUpdates) map:^id(NSNumber *x) { 224 @strongify(self); 225 return @(x.intValue >= self.viewModel.maxPointUpdates); 226 }]; 227 228 //only take the maxPointUpdates number of score updates 229 //skip 1 because we don‘t want the 1st value provided, only changes 230 [[[RACObserve(scoreStepper,value) skip:1] take:self.viewModel.maxPointUpdates] subscribeNext:^(id newPoints) { 231 @strongify(self); 232 self.viewModel.points = [newPoints doubleValue]; 233 self.scoreUpdates++; 234 }]; 235 236 //this signal should only trigger if we have "bad words" in our name 237 [self.viewModel.forbiddenNameSignal subscribeNext:^(NSString *name) { 238 @strongify(self); 239 UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"Forbidden Name!" 240 message:[NSString stringWithFormat:@"The name %@ has been forbidden!",name] 241 delegate:nil 242 cancelButtonTitle:@"Ok" 243 otherButtonTitles:nil]; 244 [alert show]; 245 self.viewModel.playerName = @""; 246 }]; 247 248 //let the upload(save) button only be enabled when the view model says its valid 249 RAC(uploadButton,enabled) = self.viewModel.modelIsValidSignal; 250 251 //set the control action for our button to be the ViewModels action method 252 [uploadButton addTarget:self.viewModel 253 action:@selector(uploadData:) 254 forControlEvents:UIControlEventTouchUpInside]; 255 256 //we can subscribe to the same thing in multiple locations 257 //here we skip the first 4 signals and take only 1 update 258 //and then disable/hide certain UI elements as our app 259 //only allows 5 updates 260 [[[[uploadButton rac_signalForControlEvents:UIControlEventTouchUpInside] 261 skip:(kMaxUploads - 1)] take:1] subscribeNext:^(id x) { 262 @strongify(self); 263 nameField.enabled = NO; 264 scoreStepper.hidden = YES; 265 uploadButton.hidden = YES; 266 }]; 267 268 } 269 270 - (void)reactiveCocoaBasicOperation 271 { 272 //基础操作 273 274 275 // 1.响应与信号 276 277 //##Subscription 订阅 278 // -subscribe… 279 // The -subscribe… methods give you access to the current and future values in a signal: 280 #if 0 281 RACSignal *letters = [@"A B C D E F G H I" componentsSeparatedByString:@" "].rac_sequence.signal; 282 [letters subscribeNext:^(NSString *x) { 283 NSLog(@"%@", x); 284 }]; 285 #endif 286 287 #if 0 288 // For a cold signal, side effects will be performed once per subscription: 289 __block unsigned subscriptions = 0; 290 291 RACSignal *loggingSignal = [RACSignal createSignal:^ RACDisposable * (id<RACSubscriber> subscriber) { 292 subscriptions++; 293 NSLog(@"I am here"); 294 [subscriber sendCompleted]; 295 return nil; 296 }]; 297 // Outputs: 298 // subscription 1 299 [loggingSignal subscribeCompleted:^{ 300 NSLog(@"subscription %u", subscriptions); 301 }]; 302 303 // Outputs: 304 // subscription 2 305 [loggingSignal subscribeCompleted:^{ 306 NSLog(@"subscription %u", subscriptions); 307 }]; 308 #endif 309 310 311 //##Injecting effects 注入 312 // -doNext -doCompleted -doError 313 // The -do… methods add side effects to a signal without actually subscribing to it: 314 // 添加事件,并不需要信号真正被订阅 315 #if 0 316 317 __block unsigned subscriptions = 0; 318 319 RACSignal *loggingSignal = [RACSignal createSignal:^ RACDisposable * (id<RACSubscriber> subscriber) { 320 subscriptions++; 321 [subscriber sendCompleted]; 322 // [subscriber sendNext:nil]; 323 return nil; 324 }]; 325 326 // Does not output anything yet 327 loggingSignal = [loggingSignal doCompleted:^{ 328 NSLog(@"about to complete subscription %u", subscriptions); 329 }]; 330 331 loggingSignal = [loggingSignal doNext:^(id x) { 332 NSLog(@"about to next subscription %u", subscriptions); 333 }]; 334 335 [loggingSignal subscribeNext:^(id x) { 336 NSLog(@"subscription %u",subscriptions); 337 }]; 338 // Outputs: 339 // about to complete subscription 1 340 // subscription 1 341 [loggingSignal subscribeCompleted:^{ 342 NSLog(@"subscription %u", subscriptions); 343 }]; 344 #endif 345 346 // 2.信号流变换 These operators transform a single stream into a new stream. 347 348 //## Mapping 映射 349 // The -map: method is used to transform the values in a stream, and create a new stream with the results: 350 // 将流的值转换为新的值后再次放入流中 351 #if 0 352 RACSequence *letters = [@"A B C D E F G H I" componentsSeparatedByString:@" "].rac_sequence; 353 354 // Contains: AA BB CC DD EE FF GG HH II 355 RACSequence *mapped = [letters map:^(NSString *value) { 356 return [value stringByAppendingString:value]; 357 }]; 358 359 [mapped.signal subscribeNext:^(NSString *str) { 360 NSLog(@"%@",str); 361 }]; 362 #endif 363 364 365 //##Filtering 过滤 366 // -filter: 367 // 使用blcok过滤流,使得能通过测试的流通过 368 #if 0 369 RACSequence *numbers = [@"1 2 3 4 5 6 7 8 9" componentsSeparatedByString:@" "].rac_sequence; 370 371 // Contains: 2 4 6 8 372 RACSequence *filtered = [numbers filter:^ BOOL (NSString *value) { 373 return (value.intValue % 2) == 0; 374 }]; 375 [filtered.signal subscribeNext:^(id x) { 376 NSLog(@"%@",x); 377 }]; 378 #endif 379 380 381 // 3.流的组合 These operators combine multiple streams into a single new stream 382 383 //##Concatenating 连接 384 // -concat: 385 // 将一个信号添加到另一个信号后面 386 #if 0 387 RACSequence *letters = [@"A B C D E F G H I" componentsSeparatedByString:@" "].rac_sequence; 388 RACSequence *numbers = [@"1 2 3 4 5 6 7 8 9" componentsSeparatedByString:@" "].rac_sequence; 389 390 // Contains: A B C D E F G H I 1 2 3 4 5 6 7 8 9 391 RACSequence *concatenated = [letters concat:numbers]; 392 [concatenated.signal subscribeNext:^(id x) { 393 NSLog(@"%@",x); 394 395 }]; 396 397 #endif 398 399 //##Flattening 压扁 400 // -flatten 401 // Flattn操作应用在"流中的流",将其组合并形成一个新的流 402 403 #if 0 404 405 RACSubject *letters = [RACSubject subject]; 406 RACSubject *numbers = [RACSubject subject]; 407 RACSignal *signalOfSignals = [RACSignal createSignal:^ RACDisposable * (id<RACSubscriber> subscriber) { 408 // 其中letters为信号,letters中的值为信号中的信号,如果不进行压扁,者不能获得真正的信号 409 [subscriber sendNext:letters]; 410 [subscriber sendNext:numbers]; 411 [subscriber sendCompleted]; 412 return nil; 413 }]; 414 415 RACSignal *flattened = [signalOfSignals flatten]; 416 417 // Outputs: A 1 B C 2 418 [flattened subscribeNext:^(NSString *x) { 419 NSLog(@"%@", x); 420 }]; 421 422 //Outputs : <RACSubject: 0x7a1f9ff0> name: 423 // <RACSubject: 0x7a17fdd0> name: 424 425 [signalOfSignals subscribeNext:^(id x) { 426 NSLog(@"%@",x); 427 }]; 428 429 [letters sendNext:@"A"]; 430 [numbers sendNext:@"1"]; 431 [letters sendNext:@"B"]; 432 [letters sendNext:@"C"]; 433 [numbers sendNext:@"2"]; 434 435 #endif 436 437 438 //##Mapping and flattening 439 // -flattenMap: 440 // it‘s -map: followed by -flatten. 441 442 #if 0 443 RACSequence *numbers = [@"1 2 3 4 5 6 7 8 9" componentsSeparatedByString:@" "].rac_sequence; 444 445 // Contains: 1 1 2 2 3 3 4 4 5 5 6 6 7 7 8 8 9 9 446 RACSequence *extended = [numbers flattenMap:^(NSString *num) { 447 return @[ num, num ].rac_sequence; 448 }]; 449 450 // Contains: 1_ 3_ 5_ 7_ 9_ 451 RACSequence *edited = [numbers flattenMap:^(NSString *num) { 452 if (num.intValue % 2 == 0) { 453 return [RACSequence empty]; 454 } else { 455 NSString *newNum = [num stringByAppendingString:@"_"]; 456 return [RACSequence return:newNum]; 457 } 458 }]; 459 // [extended.signal subscribeNext:^(id x) { 460 // NSLog(@"%@",x); 461 // }]; 462 463 [edited.signal subscribeNext:^(id x) { 464 NSLog(@"%@",x); 465 }]; 466 467 #endif 468 469 // 4.信号的组合 These operators combine multiple signals into a single new RACSignal. 470 471 //##Sequencing 472 // -then: 473 // starts the original signal, waits for it to complete, and then only forwards the values from a new signal: 474 #if 0 475 RACSignal *letters = [@"A B C D E F G H I" componentsSeparatedByString:@" "].rac_sequence.signal; 476 // The new signal only contains: 1 2 3 4 5 6 7 8 9 477 // But when subscribed to, it also outputs: A B C D E F G H I 478 RACSignal *sequenced = [[letters 479 doNext:^(NSString *letter) { 480 NSLog(@"%@", letter); 481 }] 482 then:^{ 483 return [@"1 2 3 4 5 6 7 8 9" componentsSeparatedByString:@" "].rac_sequence.signal; 484 }]; 485 486 [sequenced subscribeNext:^(id x) { 487 NSLog(@"%@",x); 488 }]; 489 #endif 490 491 //##Merging 合并 492 // +merge: 493 // 将多个信号转发到一个信号流,一旦信号到达立即转发 494 #if 0 495 496 RACSubject *letters = [RACSubject subject]; 497 RACSubject *numbers = [RACSubject subject]; 498 RACSignal *merged = [RACSignal merge:@[ letters, numbers ]]; 499 500 // Outputs: A 1 B C 2 501 [merged subscribeNext:^(NSString *x) { 502 NSLog(@"%@", x); 503 }]; 504 505 [letters sendNext:@"A"]; 506 [numbers sendNext:@"1"]; 507 [letters sendNext:@"B"]; 508 [letters sendNext:@"C"]; 509 [numbers sendNext:@"2"]; 510 511 #endif 512 513 514 //##Combining latest values 515 //+combineLatest: 516 // 方法将观察多个信号的变化,然后发送最新的值发生改变时 517 // 每个信号至少有一个值时才会转发,而merge是立即转发 518 #if 0 519 RACSubject *letters = [RACSubject subject]; 520 RACSubject *numbers = [RACSubject subject]; 521 RACSignal *combined = [RACSignal 522 combineLatest:@[ letters, numbers ] 523 reduce:^(NSString *letter, NSString *number) { 524 return [letter stringByAppendingString:number]; 525 }]; 526 527 // Outputs: B1 B2 C2 C3 528 [combined subscribeNext:^(id x) { 529 NSLog(@"%@", x); 530 }]; 531 532 [letters sendNext:@"A"]; 533 [letters sendNext:@"B"]; 534 [numbers sendNext:@"1"]; 535 [numbers sendNext:@"2"]; 536 [letters sendNext:@"C"]; 537 [numbers sendNext:@"3"]; 538 539 #endif 540 541 //##Switching 542 // -switchToLatest 543 // 作用与信号中的信号,并且转发最后的值 544 #if 0 545 RACSubject *letters = [RACSubject subject]; 546 RACSubject *numbers = [RACSubject subject]; 547 RACSubject *signalOfSignals = [RACSubject subject]; 548 549 RACSignal *switched = [signalOfSignals switchToLatest]; 550 551 // Outputs: A B 1 D 552 [switched subscribeNext:^(NSString *x) { 553 NSLog(@"%@", x); 554 }]; 555 556 [signalOfSignals sendNext:letters]; 557 [letters sendNext:@"A"]; 558 [letters sendNext:@"B"]; 559 560 [signalOfSignals sendNext:numbers]; 561 [letters sendNext:@"C"]; 562 [numbers sendNext:@"1"]; 563 564 [signalOfSignals sendNext:letters]; 565 [numbers sendNext:@"2"]; 566 [letters sendNext:@"D"]; 567 568 #endif 569 570 } 571 572 -(void)gestureRecognizerReceivedTouch:(UIPanGestureRecognizer *)recognizer { 573 if (recognizer.state == UIGestureRecognizerStateBegan) { 574 [self.gestureRecognizerIsRunningSubject sendNext:@(YES)]; 575 } 576 577 else if (recognizer.state == UIGestureRecognizerStateChanged) { 578 [self.gestureRecognizerValueSubject sendNext:[NSValue valueWithCGPoint:[recognizer locationInView:self.view]]]; 579 } 580 581 else if (recognizer.state == UIGestureRecognizerStateEnded) { 582 [self.gestureRecognizerIsRunningSubject sendNext:@(NO)]; 583 } 584 } 585 586 587 - (void)didReceiveMemoryWarning { 588 [super didReceiveMemoryWarning]; 589 // Dispose of any resources that can be recreated. 590 } 591 592 /* 593 #pragma mark - Navigation 594 595 // In a storyboard-based application, you will often want to do a little preparation before navigation 596 - (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender { 597 // Get the new view controller using [segue destinationViewController]. 598 // Pass the selected object to the new view controller. 599 } 600 */ 601 602 @end
时间: 2024-10-03 04:52:53