这节课主要讲四种特性:Modal View Controllers、UITextField and UITextView、UIView Animation和NSTimer。
Modal View Controllers
它是一个模式,当用户要继续做某些事情的时候必须先做别的事情,例如以下app:
如何使Modal View Controllers出现在屏幕上?用segue,从一个button或bar button或什么的control-drag出来,来触发Modal View Controllers。点击之后,在xcode中control-drag以创建一个segue,然后将其设置为modal,当你inspect这个segue,在这里可以设置类似它的出现方式之类的东西。
也可以从代码中放上Modal View Controllers,方法是:在xcode中创建view controller,但不是control-drag到这里,而是只要给它取个名字。记住,所有的view controller都有一个标识符字段。然后就可以创建view controller了。
- (IBAction)lookupAddress { AddressLookupViewController *alvc = [self.storyboard instantiateViewControllerWithIdentifier:@“AddressLookup”]; [self presentModalViewController:alvc animated:YES completion:^{ // alvc is now on screen; often we do nothing here }]; }
一旦有想要呈现的东西,只需要调用此方法。这边还有个completion block,当Modal View Controllers完成其动画之后会被调用。调用presentModalViewController或segue到它,屏幕将被这个Modal View Controllers完全充满,此时你的view controller无法做任何事情,它没有屏幕的访问权。
它什么时候算是全部结束呢?把Modal View Controllers放上屏幕的时候,app正在等,不能做任何事,除了Modal View Controllers想要做的。直到这个view controller调用dismissModalViewControllerAnimated,它才会结束。
- (void)dismissModalViewControllerAnimated:(BOOL)animated;
不是给被显示的view controller发送dismissModalViewControllerAnimated,而应该是给那个把Modal View Controllers放上来的对象发。
Modal View Controllers可以dismissModalViewControllerAnimated自己,所有view controller都有个方法称为presentingViewController,也就是说,先找到我的presentingViewController,并让它dismissModalViewControllerAnimated我。
[[self presentingViewController] dismissModalViewControllerAnimated:YES];
但这个做法一般是不可取的。因为把Modal View Controllers放上来是为了从用户那得到一些信息,通常present你的view controller会想要知道发生了什么事,所以你会用Modal View Controllers的delegate来告诉presenter这里发生了什么事。它将自己设置为你的delegate,你只要告诉你的delegate就好了。
然而有时只需点击取消,就没什么好反馈的,即使在这种情况下,可能还是要用一个委托方法。只需要让presentingViewController去dismissModalViewControllerAnimated你,不用任何delegate。如果Modal View Controllers什么也没做,它可能在取消的情况下没问题。但是,如果你的Modal View Controllers做了一些事情,那么你需要告诉它发生了什么事,你需要使用delegation来告诉。你将需要写自己的委托方法,这里是做delegation的一个例子:
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender { if ([segue.identifier isEqualToString:@“Lookup Address”]) { AddressLookupViewController *alvc = (AddressLookupViewController *)segue.dest...; // other setup here alvc.delegate = self; } } // (One of) AddressLookupViewController’s delegate method(s) implemented in presenter ... - (void)addressLookupViewController:(AddressLookupViewController *)sender didSelectAddress:(Address *)anAddress { // do something with the address the user selected (anAddress) [selfdismissModalViewControllerAnimated:YES]; //takes sender off screen }
这是从segue出来的Modal View Controllers,得到了正在显示的AddressLookupViewController,然后设置它。注意这里的“alvc.delegate = self;”,在prepareForSegue里我把自己设为它的delegate。下面是它的delegate方法,发送addressLookupViewController:didSelectAddress给我。这就是如果它不取消的情况下,该去dismiss的时候了。
Modal View Controllers是如何在屏幕上显示的?除了从底部滑出,还有其他的方式。
@property UIModalTransitionStyle modalTransitionStyle; UIModalTransitionStyleCoverVertical //slides up and down from bottom of screen UIModalTransitionStyleFlipHorizontal // flips the current view controller view over to modal UIModalTransitionStyleCrossDissolve //old fades out as new fades in UIModalTransitionStylePartialCurl // only if presenter is full screen (and no more modal)
在iPad上还有另一种方式,在iphone上Modal View Controllers始终覆盖整个屏幕,但在ipad上是没有必要的。
@property UIModalPresentationStyle modalPresentationStyle; // in the modal VC UIModalPresentationFullScreen // full screen anyway (always on iPhone/iPod Touch) UIModalPresentationPageSheet // full screen height, but portrait width even if landscape UIModalPresentationFormSheet // centered on the screen (all else dimmed) UIModalPresentationCurrentContext // parent’s context (e.g. in a popover)
UITextField
键盘出现的方式是当一个TextField或TextView成为first responder,first responder意味这个view里的这个闪烁的文本插入符。当用户点击键盘,字符会出现在闪烁的插入符的位置,所以当任何TextField或TextView成为first responder时,其光标开始闪烁,键盘就会出现。
可以通过代码强制键盘消失,只要发送resignFirstResponder给这个闪烁的插入符。基本上,如果闪烁的插入符消失,键盘消失,键盘只在闪烁的插入符在的时候才出现。
TextField是用来编辑的,通过点击键盘上的return,或在代码里dismiss键盘,它不再是first responder。要如何取出它的文字?可以在用户在TextField里输入每个字符的时候获得文本,它有delegate方法,从TextField获得文本的方式是使用其delegate,这个delegate就是用来取出文本的。有两种主要的方法:
- (BOOL)textFieldShouldReturn:(UITextField*)sender; //sent when return key is pressed - (void)textFieldDidEndEditing:(UITextField *)sender;
textFieldDidEndEditing是插入符消失时发送到delegate的,用户要让键盘消失,或他们点击了return,这通常就是提取到了文本并要用它做什么的时候。textFieldShouldReturn返回布尔值,表示现在是否可以点return,这允许对文本字段中的内容进行验证。
插入符出现时键盘就会出现,这样就可以控制键盘的外观了,TextField实现UITextInputTraits这个protocol。以下这些都是通过UITextInputTraits控制的,也就是UITextField的property:
@property UITextAutocapitalizationType autocapitalizationType; // words, sentences, etc. @property UITextAutocorrectionType autocorrectionType; // UITextAutocorrectionTypeYES/NO @property UIReturnKeyType returnKeyType; // Go, Search, Google, Done, etc. @property BOOL secureTextEntry; // for passwords, for example @property UIKeyboardType keyboardType; // ASCII, URL, PhonePad, etc.
要注意的是,键盘从底部出来,它覆盖了view。得确保插入符引起的键盘出来后,TextField仍然是可见的。那么怎么做呢?必须注册这些通知,例如UIKeyboardWillShowNotification、UIKeyboardDidHideNotification等。
UITextView
UITextView和UITextField之间的区别是什么?UITextView和UITextField一样是多行、可滚动、可编辑的,它更多用来显示大段文字,UITextField更像是一个可编辑的label,UITextField是用来显示很短的信息。当你点击UITextFiel的return,通常是 resignFirstResponder;而当你点击UITextView的return,它会去到下一行。
UITextViewDelegate类似于UITextField的,编辑开始和结束还有内容变化的时候,你会得到通知并做出反应。
还有一个特殊的ScrollView方法,UITextView是个UIScrollView,它有个方法scrollRangeToVisible,你指定文字NSRange的后,它会自动滚动到那部分。
- (void)scrollRangeToVisible:(NSRange)rangeOfCharactersToScrollToVisible;
UIView Animation
UIView Animation允许你设置UIView的某些属性,比如frame、是否隐藏、transform是关于缩放旋转的、还有opacity,虽然它马上就设置好了这些东西,但它实际上要过段时间才会出现在屏幕上,它在屏幕上会有动画。视觉效果发生在另一个线程,它不会阻塞主线程。
完全透明的东西就隐藏了,是不能互动的。
动画要怎么做?这都是通过UIView类方法和block。UIView类方法不是实例方法,工作原理是:这个类方法,需要一个block,在该block内,可以改变任何view的参数。所有这些变化会立即生效,但动画要一个过程。还有方法可以设定持续时间、延迟开始时间。动画结束的时候执行另一个block,
Core Animation是一个动画地改变property的机制。
下面是一个最主要的方法:
+ (void)animateWithDuration:(NSTimeInterval)duration delay:(NSTimeInterval)delay options:(UIViewAnimationOptions)options animations:(void (^)(void))animations completion:(void (^)(BOOL finished))completion;
duration就是动画的持续时间;delay是先延迟一定时间然后再开始动画;animations,这是个block,可以在里面改变任何你想改的属性;completion,这将告诉你动画是否被中断或动画是否完成。
这边有一个变化透明度的例子:
[UIView animateWithDuration:3.0
delay:0.0 options:UIViewAnimationOptionBeginFromCurrentState
animations:^{ myView.alpha = 0.0; }
completion:^(BOOL fin) { if (fin) [myView removeFromSuperview]; }];
这会让myView用3秒时间从完全不透明淡出到透明。如果在3秒内有人把alpha改成了非0,那么这个finished会是false。这是一种将view淡出后移除的方法,但如果淡出过程中别人设置了alpha为非0,那么就无法完成,因此它不会被移除。下面是一个2秒后再启动动画的例子:
if (myView.alpha == 1.0) { [UIView animateWithDuration:3.0
delay:2.0 options:UIViewAnimationOptionBeginFromCurrentState
animations:^{ myView.alpha = 0.0; }
completion:nil]; NSLog(@“alpha is %f.”, myView.alpha); }
输出的alpha值会是0,因为animation block是立即执行的,它不是异步执行。它的外观会动画,但实际的改变是立即发生的,所以alpha会是0。
如果你的动画会改变view的层级关系,那么要使用不同的方法,就是transitionFromView或transitionWithView:
+ (void)transitionFromView:(UIView *)fromView toView:(UIView *)toView duration:(NSTimeInterval)duration options:(UIViewAnimationOptions)options completion:(void (^)(BOOL finished))completion;
fromView已经是在view层级里了,把它取出并把toView放进去。动画就是删除fromView,加入toView。
+ (void)transitionWithView:(UIView *)view duration:(NSTimeInterval)duration options:(UIViewAnimationOptions)options animations:(void (^)(void))animations completion:(void (^)(BOOL finished))completion;
transitionWithView就是要把它的某些subViews隐藏,或别的animation block里的指令。
NSTimer
只需要调用scheduledTimerWithTimeInterval这个方法:
NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:(NSTimeInterval)seconds
target:self selector:@selector(doSomething:)
userInfo:(id)anyObject repeats:(BOOL)yesOrNo];
这就是从现在开始过多少秒后它要执行这一次,调用target的selector或者选择repeat为yes,那么这就是循环调用的间隔时间。userInfo是你想传递的任何信息。
这不是实时的,这仅仅是把东西放到main queue上,把它看成是那将一个会调用该方法block被放到了main queue上,但它会等一定的时间间隔。不要在timer上做很多重型工作,因为你不能阻塞main queue,它不适合做这种频繁程度的事情。
一定要在view离开屏幕的时候让timer无效。这个doSomething方法,它唯一的参数是timer,因此doSomething被调用时timer会被传递回来。
Demo
在xcode中创建一个新的项目,叫做Kitchen Sink。
UIView有一个选项,叫Clip subviews,如果打开这个,这意味着如果有一个subview部分超出了边界,会把它剪切掉。
AskerViewController.h文件代码:
#import <UIKit/UIKit.h> @class AskerViewController; @protocol AskerViewControllerDelegate <NSObject> - (void)askerViewController:(AskerViewController *)sender didAskQuestion:(NSString *)question andGotAnswer:(NSString *)answer; @end @interface AskerViewController : UIViewController @property (nonatomic, copy) NSString *question; @property (nonatomic, copy) NSString *answer; @property (nonatomic, weak) id <AskerViewControllerDelegate> delegate; @end
AskerViewController.m文件代码:
#import "AskerViewController.h" @interface AskerViewController() <UITextFieldDelegate> @property (weak, nonatomic) IBOutlet UILabel *questionLabel; @property (weak, nonatomic) IBOutlet UITextField *answerTextField; @end @implementation AskerViewController @synthesize questionLabel = _questionLabel; @synthesize answerTextField = _answerTextField; @synthesize question = _question; @synthesize answer = _answer; @synthesize delegate = _delegate; - (void)setQuestion:(NSString *)question { _question = question; self.questionLabel.text = question; } - (void)setAnswer:(NSString *)answer { _answer = answer; self.answerTextField.placeholder = answer; } - (void)viewDidLoad { [super viewDidLoad]; self.questionLabel.text = self.question; self.answerTextField.placeholder = self.answer; self.answerTextField.delegate = self; } - (void)viewWillAppear:(BOOL)animated { [super viewWillAppear:animated]; [self.answerTextField becomeFirstResponder]; } - (void)textFieldDidEndEditing:(UITextField *)textField { self.answer = textField.text; if (![textField.text length]) { [[self presentingViewController] dismissModalViewControllerAnimated:YES]; } else { [self.delegate askerViewController:self didAskQuestion:self.question andGotAnswer:self.answer]; } } - (BOOL)textFieldShouldReturn:(UITextField *)textField { if ([textField.text length]) { [textField resignFirstResponder]; return YES; } else { return NO; } } - (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation { return YES; } - (void)viewDidUnload { [self setQuestionLabel:nil]; [self setAnswerTextField:nil]; [super viewDidUnload]; } @end
KitchenSinkViewController.h文件代码:
#import <UIKit/UIKit.h> @interface KitchenSinkViewController : UIViewController @end
KitchenSinkViewController.m文件代码:
#import "KitchenSinkViewController.h" #import "AskerViewController.h" @interface KitchenSinkViewController() <AskerViewControllerDelegate> @property (weak, nonatomic) IBOutlet UIView *kitchenSink; @end @implementation KitchenSinkViewController @synthesize kitchenSink = _kitchenSink; - (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender { if ([segue.identifier hasPrefix:@"Create Label"]) { AskerViewController *asker = (AskerViewController *)segue.destinationViewController; asker.question = @"What do you want your label to say?"; asker.answer = @"Label Text"; asker.delegate = self; } } - (void)setRandomLocationForView:(UIView *)view { [view sizeToFit]; CGRect sinkBounds = CGRectInset(self.kitchenSink.bounds, view.frame.size.width/2, view.frame.size.height/2); CGFloat x = arc4random() % (int)sinkBounds.size.width + view.frame.size.width/2; CGFloat y = arc4random() % (int)sinkBounds.size.height + view.frame.size.height/2; view.center = CGPointMake(x, y); } - (IBAction)tap:(UITapGestureRecognizer *)gesture { CGPoint tapLocation = [gesture locationInView:self.kitchenSink]; for (UIView *view in self.kitchenSink.subviews) { if (CGRectContainsPoint(view.frame, tapLocation)) { [UIView animateWithDuration:4.0 animations:^{ [self setRandomLocationForView:view]; }]; } } } - (void)addLabel:(NSString *)text { UILabel *label = [[UILabel alloc] init]; label.text = text; label.font = [UIFont systemFontOfSize:48.0]; label.backgroundColor = [UIColor clearColor]; [self setRandomLocationForView:label]; [self.kitchenSink addSubview:label]; } - (void)askerViewController:(AskerViewController *)sender didAskQuestion:(NSString *)question andGotAnswer:(NSString *)answer { [self addLabel:answer]; [self dismissModalViewControllerAnimated:YES]; } - (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation { return YES; } @end