I os 062. 內容⼤大綱
• 時間控制 Timer
• QV063:基本的 Timer 應⽤用 (⼩小時鐘)
• QV064:指定 Timer 物件 (碼錶控制)
• QV065:精確的時間計算 (碼錶)
• UIView 物件
• QV077:UIView 的操作⽅方法
• QV030:UIView 的 tag 屬性⽤用法
• QV069:UISegmented Control
• NSMutableArray 陣列
• QV078:數字快按遊戲
5. NSTimer 原理 (1)
• Cocoa Touch 的 Foundation 框架內的類別
• 與 NSRunLoop (主要執⾏行迴圈) 配合來處
理定時和排程⼯工作
• 可直接使⽤用主執⾏行緒的 run loop,也能
⼿手動執⾏行其他⾮非主執⾏行緒的 run loop
6. NSTimer 原理 (2)
• 計時器並⾮非即時的機制,是在 run loop
運作且被觸發的時間到了,才會被觸發
• 設定的時間『不是固定保証會執⾏行』,
⽽而是『最⼩小的間隔時間』,或是『最早
的觸發時間』
8. NSTimer 觸發誤差 (2)
預計 預計 預計
開始計時
第⼀一次觸發 第⼆二次觸發 第三次觸發
預計觸發時間
不受前次的影
實際觸發 實際觸發
(第⼀一次) (第⼆二次)
第⼆二次觸發時間超過
第三次預計觸發時間
(不會重覆兩次)
9. NSTimer 使⽤用
• 很簡單,只要指定
• 間隔的時間 (秒)
• 要執⾏行的函式 (target, method, selector)
• 是否要重覆 (repeat)
• (進階) 傳遞⼀一些資料給 Timer 函式
• 可直接引⽤用,可以不⽤用移除
• 可指定多個計時器
16. ViewController.m (1/2)
- (void)showTime:(NSTimer *)theTimer
{
// 顯⽰示時間
NSDateFormatter *dateFormat = [[NSDateFormatter alloc] init];
[dateFormat setDateFormat:@"yyyy-MM-dd HH:mm:ss"];
NSDate *now = [[NSDate alloc] init];
NSString *nowString = [dateFormat stringFromDate:now];
myLabel1.text = nowString;
// 顯⽰示計數
count++;
myLabel2.text = [NSString stringWithFormat:@"%d", count];
// 從計數改成時分秒的顯⽰示 (不建議採⽤用此種不精確的⽅方式)
int ms, ss, mm, hh;
ms = count % 100;
ss = (int)(count / 100) % 60;
mm = (int)(count / 6000) % 3600;
hh = (int)(count / 360000) % 86400;
myLabel3.text = [NSString stringWithFormat:@"%02d:%02d:%02d.%02d",
hh, mm, ss, ms];
}
17. ViewController.m (2/2)
⼀一些額外的練習
// 其他練習
// ⽤用 C 語⾔言的⽅方法取得時間及格式,當然是不建議
time_t currentTime = time(NULL);
struct tm timeStruct;
localtime_r(¤tTime, &timeStruct);
char buffer[20];
strftime(buffer, 20, "%d-%m-%Y %H:%M:%S", &timeStruct);
myLabel1.text = [NSString stringWithFormat:@"%s", buffer];
// 在內部讓 timer 停⽌止
if(count>=500)
{
[theTimer invalidate];
theTimer = nil;
}
22. - (void)viewDidLoad ViewController.m (1/2)
{
[super viewDidLoad];
count = 0;
}
- (void) showDisplay
{
count++;
display.text = [NSString stringWithFormat:@"%d", count];
}
- (IBAction) startCount:(id)sender;
{
if(!timer)
這個檢查務必要有
{
timer = [NSTimer scheduledTimerWithTimeInterval:0.01 target:self
selector:@selector(showDisplay) userInfo:nil repeats:YES];
}
}
- (IBAction) resetCount:(id)sender
{
[timer invalidate];
timer = nil;
這個指定務必要有
count = 0;
display.text = [NSString stringWithFormat:@"%d", count];
}
23. ViewController.m (1/2)
- (IBAction) pauseCount:(id)sender
{
if([timer isValid])
{
[btnPause setTitle:@"繼續" forState:UIControlStateNormal];
[timer invalidate];
}
else
{
[btnPause setTitle:@"暫停" forState:UIControlStateNormal];
timer = [NSTimer scheduledTimerWithTimeInterval:0.01
target:self
selector:@selector(showDisplay)
userInfo:nil
repeats:YES];
}
}
27. TimerElapsed.h
TimerElapsed 類別宣告
#import <Foundation/Foundation.h>
@interface TimerElapsed : NSObject
{
NSDate *begin;
NSDate *end;
}
- (void) beginPoint;
- (void) endPoint;
- (double) timeElapsedInMilliseconds;
- (double) timeElapsedInSeconds;
- (double) timeElapsedInMinutes;
@end
28. #import "TimerElapsed.h"
TimerElapsed.m (1/2)
@implementation TimerElapsed
- (id) init
{
self = [super init];
if (self != nil)
{
begin = nil; TimerElapsed 類別的實作
end = nil;
}
return self;
}
- (void) beginPoint
{
begin = [NSDate date];
}
- (void) endPoint
{
end = [NSDate date];
}
...... 未完 ......
29. TimerElapsed.m (2/2)
...... 前有程式 ......
- (double) timeElapsedInSeconds
{
return [end timeIntervalSinceDate:begin];
}
- (double) timeElapsedInMilliseconds
{
return [self timeElapsedInSeconds] * 1000.0f;
}
- (double) timeElapsedInMinutes
{
return [self timeElapsedInSeconds] / 60.0f;
}
@end
30. TimeElapsed 類別的 method
- (void) beginPoint (設定計時開始點)
- (void) endPoint (設定計時結束點)
- (double) timeElapsedInSeconds
(傳回兩個時間點間之時間,單位為秒)
- (double) timeElapsedInMilliseconds
(傳回兩個時間點間之時間,單位為毫秒)
- (double) timeElapsedInMinutes
(傳回兩個時間點間之時間,單位為分)
31. 單純測試 TimerElapsed 類別的功能
ViewController.m
- (void)viewDidLoad
{
[super viewDidLoad];
// timerElapsed = [[TimerElapsed alloc] init];
// 測試 TimerElapsed 的功能
TimerElapsed *timerElapsed = [[TimerElapsed alloc] init];
[timerElapsed beginPoint];
// Do some work
[timerElapsed endPoint];
NSLog(@"Total time was: %lf milliseconds",
[timerElapsed timeElapsedInMilliseconds]);
NSLog(@"Total time was: %lf seconds",
[timerElapsed timeElapsedInSeconds]);
NSLog(@"Total time was: %lf minutes",
[timerElapsed timeElapsedInMinutes]);
}
34. 結合⾺馬錶開關的範例碼
ViewController.m
- (IBAction) timerStart:(id)sender
{
[timerElapsed beginPoint];
if(!timer)
{
timer = [NSTimer scheduledTimerWithTimeInterval:0.01 target:self
selector:@selector(showDisplay) userInfo:nil repeats:YES];
}
}
- (IBAction) timerStop:(id)sender
{
[timerElapsed endPoint];
[timer invalidate];
timer = nil;
}
- (void) showDisplay
{
[timerElapsed endPoint];
display.text = [NSString stringWithFormat:@"%f",
[timerElapsed timeElapsedInSeconds]];
}
35. NSTimer 記憶體使⽤用問題
Note also the point on Memory Management at the bottom of the article:
Because the run loop maintains the timer, from the perspective of memory
management there's typically no need to keep a reference to a timer
after you’ve scheduled it. Since the timer is passed as an argument
when you specify its method as a selector, you can invalidate a
repeating timer when appropriate within that method. In many
situations, however, you also want the option of invalidating the timer—
perhaps even before it starts. In this case, you do need to keep a
reference to the timer, so that you can send it an invalidate message
whenever appropriate. If you create an unscheduled timer (see
“Unscheduled Timers”), then you must maintain a strong reference to the
timer (in a reference-counted environment, you retain it) so that it is not
deallocated before you use it.
37. 參考資料補充
• How do I use NSTimer
(http://stackoverflow.com/questions/1449035/how-do-i-use-nstimer)
• 計算碼錶經過的時間差
(http://stackoverflow.com/questions/3519562/how-do-i-write-a-timer-in-
objective-c)
• 在 NSTimer 中使⽤用 userInfo
(http://www.adamzucchi.com/blog/?p=41)
42. View Coordinates
CGFloat
CGPoint
C struct with two CGFloats in it: x and y.
CGPoint p = CGPointMake(34.5, 22.0);
p.x += 20; / move right by 20 points
/
CGSize
C struct with two CGFloats in it: width and height.
CGSize s = CGSizeMake(100.0, 200.0);
s.height += 50; / make the size 50 points taller
/
CGRect
C struct with a CGPoint origin and a CGSize size.
CGRect aRect = CGRectMake(45.0, 75.5, 300, 500);
aRect.size.height += 45; / make the rectangle 45 points taller
/
aRect.origin.x += 30; / move the rectangle to the right 30 points
/
44. 建⽴立 UIView
• 由 Interface Builder 建⽴立
• 由程式產⽣生
CGRect viewRect = CGRectMake(0, 0, 100, 100);
UIView* myView = [[UIView alloc] initWithFrame:viewRect];
45. UIView Properties
• alpha, hidden, opaque, backgroundColor
• bounds, frame, center
• transform
• autoresizingMask, autoresizesSubviews
• contentMode, contentStretch, contentScaleFactor
• gestureRecognizers, userInteractiionEnabled,
multipleTouchEnabled, exclusiveTouch
• subviews, drawRect:, layer
46. UIView tag 屬性
• tag 屬性,為⼀一整數值
• 取得特定物件的⽅方法 viewWithTab:
• 速度快
• 預設 tag 為 0
47. UIView 增加、刪除與移動
• addSubview:
• insertSubview...
• bringSubviewToFront:
• sendSubviewToBack:
• exchangeSubviewAtIndex:
withSubviewAtIndex:
• removeFromSuperview
48. UIView 隱藏
• 常⽤用⽅方法
• alpha (0.0 ~ 1.0) (適合⽤用在動畫效果上)
• hidden (YES / NO)
• 隱藏的 UIView 物件
• 不會接受 touch 觸控
• 仍會隨著畫⾯面階層 autoresizing
• ⺫⽬目前為 first responder 的物件不會⾃自動 resign
52. @interface ViewController : UIViewController
{ ViewController.h
IBOutlet UIView *viewR;
IBOutlet UIView *viewG;
IBOutlet UIView *viewB;
}
- (IBAction) buttonAddR:(id)sender;
- (IBAction) buttonAddG:(id)sender; 畫⾯面三個顏⾊色區塊
- (IBAction) buttonAddB:(id)sender;
- (IBAction) buttonRemoveR:(id)sender;
- (IBAction) buttonRemoveG:(id)sender;
- (IBAction) buttonRemoveB:(id)sender;
- (IBAction) buttonToFrontR:(id)sender;
- (IBAction) buttonToFrontG:(id)sender;
- (IBAction) buttonToFrontB:(id)sender;
- (IBAction) buttonToBackR:(id)sender;
- (IBAction) buttonToBackG:(id)sender;
- (IBAction) buttonToBackB:(id)sender;
- (IBAction) change01:(id)sender;
- (IBAction) moveLastThree:(id)sender;
@end
53. ViewController.m (1/2)
- (IBAction) buttonAddR:(id)sender
{
[self.view addSubview:viewR];
}
- (IBAction) buttonRemoveR:(id)sender
{
[viewR removeFromSuperview];
與層次順序有關之
} 主要 method
- (IBAction) buttonToFrontR:(id)sender
{
[self.view bringSubviewToFront:viewR];
}
- (IBAction) buttonToBackR:(id)sender
{
[self.view sendSubviewToBack:viewR];
}
****** 省略部份程式 ******
54. ViewController.m (2/2)
- (IBAction) change01:(id)sender
{
[self.view exchangeSubviewAtIndex:0 withSubviewAtIndex:1];
}
所有⼦子視圖
為陣列結構
- (IBAction) moveLastThree:(id)sender
{
int lastIndex = [[self.view subviews] count];
[self.view exchangeSubviewAtIndex:0 withSubviewAtIndex:(lastIndex-1)];
[self.view exchangeSubviewAtIndex:1 withSubviewAtIndex:(lastIndex-2)];
[self.view exchangeSubviewAtIndex:2 withSubviewAtIndex:(lastIndex-3)];
}
57. ViewController.h ViewController.xib
#import <UIKit/UIKit.h>
@interface ViewController :
UIViewController
{
IBOutlet UILabel *display;
IBOutlet UIButton *btn1;
IBOutlet UIButton *btn2;
IBOutlet UIButton *btn3;
IBOutlet UIButton *btn4;
IBOutlet UIButton *btn5;
}
- (IBAction)updateLabel:(id)sender;
@end
58. - (IBAction)updateLabel:(id)sender
{ ViewController.m
switch ([sender tag])
{
case 1:
display.text = @"111";
break;
case 2:
display.text = @"222";
break;
// *** 部份內容省略 ***
}
}
- (void)viewDidLoad 注意在⼀一開始做好設定
{
[super viewDidLoad];
[btn1 setTag:1];
[btn2 setTag:2];
[btn3 setTag:3];
[btn4 setTag:4];
[btn5 setTag:5];
}
64. ViewController.m
- (IBAction) buttonAction: (UISegmentedControl *) sender
{
// 取得按下何鍵
NSInteger index = sender.selectedSegmentIndex;
// 指定圖檔名
NSString *show = [NSString stringWithFormat:@"cat%d.png", index];
// 顯⽰示圖
imageView.image = [UIImage imageNamed:show];
}
67. #import <UIKit/UIKit.h> ViewController.h
static const int NUM_TOTAL = 9;
@interface ViewController : UIViewController
{
IBOutlet UILabel *scoreLabel;
存放各個位置內的
NSMutableArray *listArray;
實際 (顯⽰示) 數字
int currentNumber;
NSDate *begin; ⺫⽬目前按到哪個數字
NSDate *end;
}
- (void) beginPoint; ⽤用來記錄時間點
- (void) endPoint;
- (IBAction) buildList:(id)sender;
- (IBAction) buttonClick:(id)sender;
@end
70. ViewController.m (1/9)
程式開始的設定
- (void)viewDidLoad
{
[super viewDidLoad];
listArray = [[NSMutableArray alloc] initWithObjects:nil];
for(int i=0; i<NUM_TOTAL; i++)
{
[listArray addObject:[NSNumber numberWithInt:(i+1)]];
}
[self buildList:nil];
}
將陣列填⼊入 1~9 的數字
71. 重建數字排列,指定到各個按鍵 ViewController.m (2/9)
- (IBAction) buildList:(id)sender
{
// 陣列打散 重建陣列內容 (隨機打散)
for (int i=0; i<NUM_TOTAL-1; i++)
{
int j = i + (arc4random() % (NUM_TOTAL-i));
[listArray exchangeObjectAtIndex:i withObjectAtIndex:j];
}
// 指定各個按鈕的⽂文字
for (int i=0; i<[listArray count]; i++)
{
UIButton *button = (UIButton *)[self.view viewWithTag:(i+1)];
button.alpha = 1.0;
button.enabled = YES;
int value = [[listArray objectAtIndex:i] intValue];
[button setTitle:[NSString stringWithFormat:@"%d", value]
forState:UIControlStateNormal];
} 取得各個按鍵
****** 省略部份程式 ****** 第 i 個陣列元素值
}
72. - (IBAction) buildList:(id)sender ViewController.m (2/9)
{
****** 省略部份程式 ******
// 起始設定
currentNumber = 1;
scoreLabel.text = @""; 設定時間點 取得陣列的各個元素
// 開始時間點記錄 (指定成⼀一個字串)
[self beginPoint];
// debug
NSString *str = [[NSString alloc] initWithString:@""];
// ⽅方法⼀一
for (int i=0; i<[listArray count]; i++)
{
str = [str stringByAppendingFormat:@"%1d",
[[listArray objectAtIndex:i] integerValue]];
}
// ⽅方法⼆二
for(NSNumber *obj in listArray)
{
str = [str stringByAppendingFormat:@"%1d", [obj integerValue]];
}
NSLog(@"%@", str);
}
73. ViewController.m (1/9)
每次按鍵後的程式
-(IBAction) buttonClick:(id)sender
{
UIButton *button = (UIButton *)sender;
int value = [[[button titleLabel] text] intValue];
if(currentNumber==value)
{ 正確的按鍵,則變淡失效
button.alpha = 0.4;
button.enabled = NO;
currentNumber++;
遊戲結束的判斷
// NSLog(@"Next numbe: %d", currentNumber);
if(currentNumber>NUM_TOTAL)
{
[self endPoint];
float timeElapsed = [end timeIntervalSinceDate:begin];
scoreLabel.text = [NSString stringWithFormat:
@"Bingo!!! %6.2f 秒", timeElapsed];
}
}
}
74. 遊戲時間的計算
- (void) beginPoint ViewController.m (1/9)
{
begin = [NSDate date];
}
- (void) endPoint
{
end = [NSDate date];
}
- (IBAction) buildList:(id)sender
{ 記錄開始時間點
****** 省略部份程式 ******
[self beginPoint]; // 開始時間點記錄
}
記錄結束時間點
-(IBAction) buttonClick:(id)sender
{
****** 省略部份程式 ****** 兩個時間點的間隔
if(*** 遊戲結束的條件 ***)
{
[self endPoint];
float timeElapsed = [end timeIntervalSinceDate:begin];
scoreLabel.text = [NSString stringWithFormat:
@"Bingo!!! %6.2f 秒", timeElapsed];
}
}