SlideShare une entreprise Scribd logo
1  sur  212
iOS App Development
1
資料下載
https://dl.dropboxusercontent.com/u/14692540/iphoneui.zip
2
Who am I?
 林銘賢
 白天
 網路公司工程師
 晚上
 南台講師
 其它時間
 導航 App Navier HUD iOS 版 作者
Navier HUD iOS
Navier HUD iOS
Navier HUD iOS
Navier HUD iOS
課程大綱
 iOS App 基礎
 Tab Bar
 Page Control
 Table
 Gesture
 Collection
 Timer and Thread
 iAd and IAP
8
iOS App Basics
9
View 階層架構
10
View 階層架構
11
畫面坐標原點在左上角
12
Frame 與 Bounds
13
App 沙盒 (SandBox)
每個 App 都有自
獨立的目錄
14
常用目錄
15
目錄名稱 說明
<Application_Home>/<AppName>.app App bundle。不能在這個目錄寫
入任何資料
<Application_Home>/Documents 存放一般資料,不會被系統清除
<Application_Home>/Library 存放與 code 有關,但非使用者
資料的地方
<Application_Home>/tmp 暫存的目錄,會被系統清除
轉向
直立
直立
上下顛倒
橫向-左 橫向-右
16
啟動APP
17
切換 App
執行進入
背景模式
18
從背景模式切換到前景模式
19
20
視窗架構
21
視窗架構
22
實際上是由 View Controller 負責提供
UIView 給 Windows
init
loadView
viewDidLoad
viewWillAppear
viewDidAppear
viewWillDisappear
viewDidDisappear
viewDidUnload
dealloc
View Controller 的生命週期
23
View Controller 架構
24
View Controller 架構
25
26
27
28
Navigation
Push
Pop
Push
Pop
29
30
31
Modal
32
Segue
Segue
Push 把目標 View Controller 加到 Navigation 堆疊最上層
Modal 顯示目標 View Controller
Custom 由使用者自定訂義 View Controller 之間的轉換
33
Segue Push 與 Modal 的比較
 Push
 較有結構,有 Navigation 堆疊幫忙回上一層 View
Controller
 有 Navigation Bar
 需要有 Navigation View Controller
 Modal
 可任意帶出下一個 View Controller
 需自行定義 View Controller 之間的關係
34
專案設定
35
專案設定
 Xcode 5.1
 iPhone Retina 4 inch, 64 bit
36
37
設定 framework
加入
iAd.framework ( iAd 廣告)
StoreKit.framework (In App Purchase, IAP, 內購)
38
專案資料夾
39
變更預設 StoryBorad
40
設定 App Icon
41
設定 App 圖示與起始畫面
42
圖示
起始畫面
設定 App 圖示與起始畫面
43
設定起始畫面
44
Tab Bar
45
Tab Bar 範例
46
Tab Bar 架構
47
Tab Bar 設計
48
49
50
新增 Tab Bar 的 View
Controller
51
滑屬右鍵
新增 Tab Bar 的 View
Controller
52
53
Page Control
54
Page Control
55
使用到的元件
56
57
58
59
新增界面元件與 View Controller 的關係
60
選擇 Automatic, 讓 Xcode 自動幫
你列出相關的 View Controller
新增界面元件與 View Controller 的關係
61
建立 ScrollView Outlet
設定 ScrollView 的 delegate
PageViewController.h
@interface PageViewController : UIViewController<UIScrollViewDelegate>
@property (weak, nonatomic) IBOutlet UIScrollView *scrollView;
@property (weak, nonatomic) IBOutlet UIPageControl *pageControl;
@end
PageViewController.m
@interface PageViewController ()
{
NSArray* imageArray;
}
@end
62
PageViewController.m
- (void)viewDidLoad
{
[super viewDidLoad];
// 建立圖片陣列
imageArray = [[NSArray alloc] initWithObjects:
@"wallpaper1.jpg",
@"wallpaper2.jpg",
@"wallpaper3.jpg",
@"wallpaper4.jpg",
@"wallpaper5.jpg",
@"wallpaper6.jpg",
@"wallpaper7.jpg",
@"wallpaper8.jpg",
nil];
63
// 依序把圖片放到 Scroll View 的相對應位置
for (int i = 0; i < [imageArray count]; i++)
{
// 計算圖片大小及原點位置
CGRect frame;
frame.origin.x = self.scrollView.frame.size.width * i;
frame.origin.y = 0;
frame.size = self.scrollView.frame.size;
// 建立 UIImageView 物件存放圖片
UIImageView *imageView = [[UIImageView alloc] initWithFrame:frame];
imageView.image = [UIImage imageNamed:[imageArray objectAtIndex:i]];
[self.scrollView addSubview:imageView];
}
// 設定 Scroll View 的內容物大小
self.scrollView.contentSize=CGSizeMake(320*8, 460);
// 設定 Scroll View 委派物件為自己
self.scrollView.delegate = self;
} 64
PageViewController.m
// 處理捲動事件
- (void)scrollViewDidScroll:(UIScrollView *)scrollView
{
// 取得目前顯示頁面的坐標
CGFloat pageWidth = self.scrollView.frame.size.width;
// 計算出頁碼
int page = floor((self.scrollView.contentOffset.x - pageWidth / 2) / pageWidth) + 1;
// 設定頁碼
self.pageControl.currentPage = page;
}
65
UIScrollViewDelegate
Table
66
功能
 Table 資料的顯示
 顯示詳細內容
 移動
 刪除
 Navigation Bar
 跨 View Controller 之間的資料傳遞
67
68
69
70
嵌入 Navigation Controller
71
72
Table View
 UITableViewDataSource
 提供要顯示的資料
 UITableViewDelegate
 處理 TableView 上的操作及事件
73
Table View
 設定 dataSource 與 delegate
74
75
新增 Push Segue
76
新增 Push Segue
77
78
設定自訂的 View Controller 類別
 MyTableViewController
 繼承 UITableViewController
79
MyTableViewController.m
@interface MyTableViewController ()
{
NSMutableArray *titles;
NSString *selectedString;
}
@end
80
準備 Table 資料
81
MyTableViewController.m
- (void)viewDidLoad
{
NSMutableArray *section;
[super viewDidLoad];
titles = [[NSMutableArray alloc] initWithCapacity:3];
// 第一個 Section 台南
section = [NSMutableArray arrayWithObjects:
@"東區", @"中西區", @"北區", @"南區", @"永康區", @"安平區", nil];
[titles addObject:section];
// 第二個 Section 高雄
section = [NSMutableArray arrayWithObjects:@"三民", @"前鎮", @"苓雅", nil];
[titles addObject:section];
// 第三個 Section 屏東
section = [NSMutableArray arrayWithObjects:@"三地門", @"恆春", nil];
[titles addObject:section];
// 設定 Navigation Bar 的右邊按鈕為 Table View 的編輯按鈕
self.navigationItem.rightBarButtonItem = self.editButtonItem;
}
82
MyTableViewController.m
// 設定 Section 數
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return titles.count;
}
// 設定 Section 內的列數
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return ((NSMutableArray*)[titles objectAtIndex:section]).count;
}
// 設定要顯示 cell
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath
*)indexPath
{
// 透過 Identifier: DefaultCell 取得 Cell
UITableViewCell *cell =
[tableView dequeueReusableCellWithIdentifier:@"DefaultCell" forIndexPath:indexPath];
// 設定區域名稱
cell.textLabel.text =
[[titles objectAtIndex:indexPath.section] objectAtIndex:indexPath.row];
return cell;
}
83
UITableViewDataSource
UITableViewDataSource
UITableViewDataSource
MyTableViewController.m
// 設定各 Section 的名稱
- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section
{
NSString *sectionName;
switch (section)
{
case 0:
sectionName = @"台南";
break;
case 1:
sectionName = @"高雄";
break;
default:
sectionName = @"屏東";
break;
}
return sectionName;
}
84
UITableViewDataSource
處理資料編輯(刪除)
85
MyTableViewController.m
// 設定是否允許編輯列 (刪除)
- (BOOL)tableView:(UITableView *)tableView canEditRowAtIndexPath:(NSIndexPath *)indexPath
{
return YES;
}
// 確定輯輯後狀態
- (void)tableView:(UITableView *)tableView
commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath
*)indexPath
{
// 編輯的類型為『刪除』
if (editingStyle == UITableViewCellEditingStyleDelete) {
// 更新 Section 資料
NSMutableArray *section;
section = [titles objectAtIndex:indexPath.section];
[section removeObjectAtIndex:indexPath.row];
if (section.count == 0)
{ [titles removeObject:section]; }
// 更新表格狀態
[tableView deleteRowsAtIndexPaths:@[indexPath]
withRowAnimation:UITableViewRowAnimationFade];
}
86
UITableViewDelegate
UITableViewDelegate
處理資料移動
87
MyTableViewController.m
// 設定是否允許移動列
- (BOOL)tableView:(UITableView *)tableView canMoveRowAtIndexPath:(NSIndexPath *)indexPath
{
return YES;
}
// 移動列範圍判斷
- (NSIndexPath *)tableView:(UITableView *)tableView
targetIndexPathForMoveFromRowAtIndexPath:(NSIndexPath *)sourceIndexPath
toProposedIndexPath:(NSIndexPath *)proposedDestinationIndexPath
{
// 不在同一個 Secion 的話,不允許移動
if( sourceIndexPath.section != proposedDestinationIndexPath.section )
return sourceIndexPath;
else
return proposedDestinationIndexPath;
}
88
UITableViewDelegate
UITableViewDelegate
MyTableViewController.m
// 移動列
- (void)tableView:(UITableView *)tableView moveRowAtIndexPath:(NSIndexPath *)fromIndexPath
toIndexPath:(NSIndexPath *)toIndexPath
{
NSMutableArray* section;
// 判斷是否在同 Secion 內移動
if (fromIndexPath.section == toIndexPath.section)
{
NSString *fromString;
section = [titles objectAtIndex:fromIndexPath.section];
fromString = [section objectAtIndex:fromIndexPath.row];
// 把被移動的列從原本的 Secion 中刪除
[section removeObjectAtIndex:fromIndexPath.row];
// 把被移動的列加到新位置
[section insertObject:fromString atIndex:toIndexPath.row];
}
}
89
UITableViewDelegate
處理資料選擇及
跨 View Controller 之間的資料
傳遞
90
MyTableViewController.m
// 處理按下列時的事件
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
// 儲存該列的地名
selectedString = [[titles objectAtIndex:indexPath.section]
objectAtIndex:indexPath.row];
// 執行 segue, 移動到 DetailViewController
[self performSegueWithIdentifier:@"segueDetail" sender:self];
}
91
UITableViewDelegate
MyTableViewController.m
// 準備處理 Segue
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
// 判斷即將要執行的 Segue 是否為 segueDetail
if ([[segue identifier] isEqualToString:@"segueDetail"])
{
// 取得目標 ViewController
DetailViewController* detailViewController = [segue destinationViewController];
// 設定要顯示的地名
detailViewController.text = selectedString;
}
}
92
Segue
DetailViewController.h
@interface DetailViewController : UIViewController
@property (weak, nonatomic) IBOutlet UILabel *textLabel;
@property (weak, nonatomic) NSString *text;
@end
93
DetailViewController.m
- (void)viewWillAppear:(BOOL)animated
{
// 設定標籤顯示地名
self.textLabel.text = self.text;
}
Gesture
手勢處理
94
手勢類型
95
Tap 點擊 Rotation 旋轉 Pinch 縮放
手勢類型
 Swipe (掃動)
 固定方向
 上下左右
 Pan (拖拉)
 任何方向
 上下左右
 斜對角
96
處理手勢的類別
97
元件
98
Tap 點擊不連續手勢類型
99
狀態轉換
100
Pinch 縮放連續手勢類型
101
102
UIGestureRecognizerState
103
UIGestureRecognizerStateBegan 開始
UIGestureRecognizerStateChanged 改變中,連續型的才有
UIGestureRecognizerStateEnded 結束
UIGestureRecognizerStateCancelled 取消
UIGestureRecognizerStateFailed 失敗
UIGestureRecognizerStateRecognized 與UIGestureRecognizerStateEnded 相同
104
取消 Auto Layout
105
取消 Auto Layout
Tap (點擊)
106
 按一下跳至下一層
點擊次數 手指數
Tap (點擊)
 透過 Segue 直接連到 Swipe View Controller
107
Swipe (掃動)
108
透過左右滑動回到上一層或下一層
連接 Tap 與 Swipe
109
加入 Bar Button Item 並建立 Segue
110
選擇 Push
111
建立的 Segue
112
Bar Item
113
設定名稱
Swipe (掃動)
114
右到左,透過 Segue 移動 左到右,透過Sent Action 處理
SwipeViewController.h
// 處理 Swipe Gesture 由左滑到右邊事件
- (IBAction)handSwipeLeftToRight:(id)sender {
// pop 回上一層 view controller
[self.navigationController popViewControllerAnimated:TRUE];
}
115
Pan (拖拉)
116
透過左右滑動回到上一層或下一層
Pan (拖拉)
117
由 Sent Action 處理,藉由 x 方向的移動速度,判斷左右
PanViewController.h
// 處理 Pan 手勢
- (IBAction)handlePan:(id)sender {
UIPanGestureRecognizer *panGesture = (UIPanGestureRecognizer*)sender;
// 判斷是否為 pan 手勢結束狀態
if (panGesture.state == UIGestureRecognizerStateEnded)
{
// 取得移動速度
CGPoint velocity = [panGesture velocityInView:self.view];
// x > 0 為 左向右滑動,pop 回上一層 view controller
if(velocity.x > 0)
{
[self.navigationController popViewControllerAnimated:TRUE];
}
// x < 為 右向左滑動, 執行 segueLongPress
// push 下一層 view controller (Long Press)
else
{
[self performSegueWithIdentifier:@"segueLongPress" sender:self];
}
}
}
118
Long Press (長按)
 長按切換按鈕顏色
119
Long Press (長按)
120
由 Sent Action 處理
LongViewController.h
@interface LongPressViewController : UIViewController
// 處理長按手勢
- (IBAction)handleLongPress:(id)sender;
// 中央按鈕
@property (weak, nonatomic) IBOutlet UIButton *longPressButton;
@end
121
LongViewController.m
- (void)viewDidLoad
{
[super viewDidLoad];
// 設定角落的圓弧半徑為 60
self.longPressButton.layer.cornerRadius = 60;
// 設定邊界線條寬度為 0,即隱藏邊界
self.longPressButton.layer.borderWidth = 0.0f;
}
LongViewController.m
// 處理長按手勢
- (IBAction)handleLongPress:(id)sender
{
// 型態轉換
UILongPressGestureRecognizer *longPressGesture = (UILongPressGestureRecognizer*)sender;
// 長按手勢開始
if (longPressGesture.state == UIGestureRecognizerStateBegan)
{
// 按鈕綠色變紅色
if ([self.longPressButton.backgroundColor isEqual:[UIColor greenColor]])
{
self.longPressButton.backgroundColor = [UIColor redColor];
}
// 按鈕紅色變綠色
else
{
self.longPressButton.backgroundColor = [UIColor greenColor];
}
}
} 122
Pinch (縮放)
 利用 Pinch 縮放圖片大小
123
UIImageView
124
Pinch (縮放)
125
由 Sent Action 處理
PinchViewController.m
@interface PinchViewController ()
{
// 記錄上一次放大的倍數
CGFloat lastScale;
}
@end
126
PinchViewController.m
// 處理 pinch 手勢
- (IBAction)handlePinch:(UIPinchGestureRecognizer *)recognizer
{
float scale;
// 若為手勢剛開始狀態,設定 lastScale = 1.0 (無放大縮小)
if (recognizer.state == UIGestureRecognizerStateBegan)
{
lastScale = 1.0;
return;
}
// 其它狀態, 依 lastScale 的值來決定要放大多少
scale = 1 + (recognizer.scale-lastScale);
lastScale = recognizer.scale;
// 放大圖片
self.imageView.transform = CGAffineTransformScale(self.imageView.transform, scale,
scale);
}
127
Rotate (旋轉)
 旋轉圖片
128
Rotate (旋轉)
129
由 Sent Action 處理
RotationViewController.m
@interface RotationViewController ()
{
// 記錄上次旋轉角度
CGFloat lastRotation;
}
@end
130
RotationViewController.m
// 處理 Rotation 手勢
- (IBAction)handleRotation:(UIRotationGestureRecognizer *)sender
{
// 若為手勢剛開始狀態,設定 lastRotation = 0.0 (無旋轉)
if([sender state] == UIGestureRecognizerStateBegan) {
lastRotation = 0.0;
return;
}
// 其它狀態, 依 lastRotation 的值及來決定要旋轉幾度
CGFloat rotation = 0.0 - (lastRotation - [sender rotation]);
// 計算新的 transform
CGAffineTransform currentTransform = self.imageView.transform;
CGAffineTransform newTransform =
CGAffineTransformRotate(currentTransform,rotation);
// 設定新的 transform
[self.imageView setTransform:newTransform];
// 更新 lastRotation 下次使用
lastRotation = [sender rotation];
}
131
結合 Pan, Pinch, Rotation
 可移動、縮放、旋轉圖片
132
PinchRotationViewController.m
@interface PinchRotationViewController : UIViewController
// 處理 Rotation 手勢
- (IBAction)handleRotation:(UIRotationGestureRecognizer *)sender;
// 處理 pinch 手勢
- (IBAction)handlePinch:(UIPinchGestureRecognizer *)sender;
// 處理 Pan 手勢
- (IBAction)handlePan:(UIPanGestureRecognizer *)sender;
// UIImageView, 圖片 UI 物件
@property (weak, nonatomic) IBOutlet UIImageView *imageView;
@end
133
PinchRotationViewController.m
// 處理 pan 手勢
- (IBAction)handlePan:(UIPanGestureRecognizer *)sender
{
// 若是 pan 手勢進行中或結束時,更新圖片位置
if ((sender.state == UIGestureRecognizerStateChanged) ||
(sender.state == UIGestureRecognizerStateEnded)) {
// 取得手勢位置
CGPoint location = [sender locationInView:self.view];
// 設定圖片中心
self.imageView.center = location;
}
}
134
Collection
135
UICollectionViewController
136
Collection 設計
137
Collection Story Board
138
Collection View
139
勾選 Header
把 UIImageView
拉到 Cell
140
設定 Cell 及 Header 大小
141
嵌入 Navigation View Controller
142
Embedded View Controller
加入 Full Image View Controller
143
 建立 Section
Header 的類別
及識別
 建立 Cell 的類別
及識別
 建立 Full Image
View Controller
類別
144
Collection Reusable View
 設定 Identifier
145
Collection Reusable View
 設定 Identifier
146
Full Image View Controller
UIImageView 設定
147
MyCollectionViewCell.h
@interface MyCollectionViewCell : UICollectionViewCell
@property (strong, nonatomic) IBOutlet UIImageView *imageView;
@end
148
MySupplementaryView.h
@interface MySupplementaryView : UICollectionReusableView
@property (weak, nonatomic) IBOutlet UILabel *headerLabel;
@end
FullImageViewController.h
@interface FullImageViewController : UIViewController
// 要顯示的 UIImageView
@property (weak, nonatomic) IBOutlet UIImageView *imageView;
// 暫存的圖片
@property (strong, nonatomic) UIImage *image;
@end
149
FullImageViewController.m
- (void)viewWillAppear:(BOOL)animated
{
// 設定 imageView 的圖片
self.imageView.image = self.image;
}
MyCollectionViewController.m
#import "MySupplementaryView.h"
#import "FullImageViewController.h"
@interface MyCollectionViewController ()
{
// 選擇的圖片
UIImage *selectedImage;
// Secion 0 圖片名稱字串陣列
NSMutableArray *images0;
// Secion 1 圖片名稱字串陣列
NSMutableArray *images1;
}
@end
150
MyCollectionViewController.m
- (void)viewDidLoad
{
[super viewDidLoad];
// 建立 2 個 Section 的圖片名稱字串陣列
images0 = [@[@"fish1.png",
@"fish2.png",
@"fish3.png",] mutableCopy];
images1 = [@[@"fish3.png",
@"fish2.png",
@"fish1.png",] mutableCopy];
}
- (void)viewWillAppear:(BOOL)animated
{
// 隱藏 Navigation Bar
[[self navigationController] setNavigationBarHidden:YES animated:YES];
}
- (void)viewWillDisappear:(BOOL)animated
{
// 顯示 Navigation Bar
[[self navigationController] setNavigationBarHidden:NO animated:YES];
}
151
MyCollectionViewController.m
// 回傳 Section 個數, 2 個 Section
- (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView
{
return 2;
}
// 回傳 Section 裡的項目個數
- (NSInteger)collectionView:(UICollectionView *)collectionView
numberOfItemsInSection:(NSInteger)section
{
if (section == 0)
return images0.count;
return images1.count;
}
152
UICollectionViewDataSource
UICollectionViewDataSource
MyCollectionViewController.m
// 設定各 Section 的名稱
-(UICollectionReusableView *)collectionView:(UICollectionView *)collectionView
viewForSupplementaryElementOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath
{
MySupplementaryView *header = nil;
// 判斷是 header 還是 footer
if ([kind isEqual:UICollectionElementKindSectionHeader])
{
// 取得 header
header = [collectionView dequeueReusableSupplementaryViewOfKind:kind
withReuseIdentifier:@"MyHeader"
forIndexPath:indexPath];
// 設定標題
if (indexPath.section == 0)
header.headerLabel.text = @"Fish 0 Gallery";
else
header.headerLabel.text = @"Fish 1 Gallery";
}
return header;
}
153
UICollectionViewDataSource
MyCollectionViewController.m
// 設定要顯示 cell
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView
cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
UIImage *image;
// 取得 cell
MyCollectionViewCell *myCell = [collectionView
dequeueReusableCellWithReuseIdentifier:@"MyCell"
forIndexPath:indexPath];
// 依 cell 位置,建立圖片
if (indexPath.section == 0)
image = [UIImage imageNamed:images0[indexPath.row]];
else
image = [UIImage imageNamed:images1[indexPath.row]];
// 設定 cell 要顯示的圖片
myCell.imageView.image = image;
return myCell;
}
154
UICollectionViewDataSource
MyCollectionViewController.m
// 處理選擇的項目
- (void)collectionView:(UICollectionView *)collectionView
didSelectItemAtIndexPath:(NSIndexPath *)indexPath;
{
// 取得選擇的圖片
if (indexPath.section == 0)
selectedImage = [UIImage imageNamed:images0[indexPath.row]];
else
selectedImage = [UIImage imageNamed:images1[indexPath.row]];
// 執行 Segue: pushShowFullImage
[self performSegueWithIdentifier: @"pushShowFullImage" sender: self];
}
// 準備執行 Segue
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
// 判斷要執行的 Segue 是否為 pushShowFullImage
if ([[segue identifier] isEqualToString:@"pushShowFullImage"])
{
// 取得目標 View Controller
FullImageViewController *fvc = [segue destinationViewController];
// 設定 FullImageViewController 要顯示的圖片
fvc.image = selectedImage;
}
}
155
UICollectionViewDelegate
Timer and Thread
156
Clock
 使用 NSTimer 及
NSThread 實作時鐘
 利用 UISwitch 切換
開關
 使用 PickerView 選
擇時間格式
157
158
設定 UISwitch 的 Value Changed 事件
159
設定 Picker View 的 delegate 及 datasource
NSThread 限制
 透過NSThread建立出來的執行緒與控制界面的主執行緒
是不一樣的
 NSTimer 建立出來的計時器可以變更界面內容
 NSThread 逼立出來的執行緒不能變更界面內容
 需要回到主執行緒才能變更
 performSelectorOnMainThread:withObject:waitUntilDone:
 performSelectorOnMainThread:withObject:waitUntilDone:mo
des:
160
取得介面元件的另一個方法
 介面元件都有一個 Tag
 不一定要透過 Story Board 連接 View Controller 與介面
元件之間的關係
 View Controller 可以透過 Tag 在程式執行期間,動態取
得介面元件
161
[self.view viewWithTag:1]
設定 Tag
162
ClockViewController.m
@interface ClockViewController ()
{
// UI 界面元件
UILabel *clockLabel;
UISwitch *timerSwitch;
UISwitch *threadSwitch;
// 儲存 date format 字串的陣列
NSArray *dateFormat;
// 計時器
NSTimer *clockTimer;
// 執行緒
NSThread *clockThread;
// 執行緒生命判斷標籤
BOOL isThreadAlive;
// 目前使用的 date format
NSDateFormatter *dateFormatter;
}
@end
163
ClockViewController.m
- (void)viewDidLoad
{
[super viewDidLoad];
clockTimer = nil;
clockThread = nil;
isThreadAlive = FALSE;
// 透過 tag 取得 UI 介面元件
clockLabel = (UILabel*)[self.view viewWithTag:1];
timerSwitch = (UISwitch*)[self.view viewWithTag:2];
threadSwitch = (UISwitch*)[self.view viewWithTag:3];
// 設定 dateFormat 陣列
dateFormat = [NSArray arrayWithObjects:@"HH:mm:ss", @"ss", @"yyyy-MM-dd HH:mm:ss",
@"MM/dd HH:mm:ss", nil];
// 設定預設 date format 為 dateFormat 字串陣列裡的第 0 個
dateFormatter = [[NSDateFormatter alloc] init];
[dateFormatter setDateFormat:[dateFormat objectAtIndex:0]];
}
164
ClockViewController.m
// 設定 PickerView 的元件個數
- (NSInteger)numberOfComponentsInPickerView:(UIPickerView *)pickerView
{
return 1;
}
// 設定 PickerView 元件裡的列數
- (NSInteger)pickerView:(UIPickerView *)pickerView
numberOfRowsInComponent:(NSInteger)component
{
return dateFormat.count;
}
// 設定 PickerView 列要顯示的字串
- (NSString *)pickerView:(UIPickerView *)pickerView titleForRow:(NSInteger)row
forComponent:(NSInteger)component
{
return [dateFormat objectAtIndex:row];
}
// 處理選擇 PickerViw 列的事件
- (void)pickerView:(UIPickerView *)pickerView didSelectRow:(NSInteger)row
inComponent:(NSInteger)component
{
// 設定新的 date format
[dateFormatter setDateFormat:[dateFormat objectAtIndex:row]];
}
165
UIPickerViewDataSource
UIPickerViewDataSource
UIPickerViewDataSource
UIPickerViewDelegate
ClockViewController.m
// 啟動 Timer
- (void)startTimer
{
if (clockTimer == nil)
{
clockTimer =
[NSTimer scheduledTimerWithTimeInterval:1 target:self
selector:@selector(clockTimeout) userInfo:nil repeats:YES];
}
}
// 停止 Timer
- (void)stopTimer
{
if (clockTimer != nil)
{
[clockTimer invalidate];
clockTimer = nil;
}
}
// 處理 timeout 事件, 更新要顯示的時間
- (void)clockTimeout
{
// 依目前的 dateFormatter 格式顯示時間
clockLabel.text = [dateFormatter stringFromDate:[NSDate date]];
}
166
ClockViewController.m
// 啟動執行緒
- (void)startThread
{
if (clockThread == nil)
{
clockThread = [[NSThread alloc] initWithTarget:self
selector:@selector(threadMain)
object:nil];
isThreadAlive = TRUE;
[clockThread start]; // Actually create the thread
}
}
// 停止執行緒
- (void)stopThread
{
if (clockThread != nil)
{
clockThread = nil;
isThreadAlive = FALSE;
}
}
167
ClockViewController.m
// 執行緒本體
- (void)threadMain
{
while(isThreadAlive)
{
// 通知 Main Thread 執行 clockTimeout
[self performSelectorOnMainThread:@selector(clockTimeout) withObject:nil
waitUntilDone:FALSE];
// 睡 1 秒
sleep(1);
}
}
168
ClockViewController.m
// 處理 UISwitch value changed 事件
- (IBAction)handleSwitchValueChanged:(id)sender {
// 若是 timerSwitch
// 1. 停止 thread 執行
// 2. 依 timerSwitch 狀態決定要執行或是停止 timer
if (sender == timerSwitch) {
[self stopThread];
threadSwitch.on = FALSE;
if (timerSwitch.on)
[self startTimer];
else
[self stopTimer];
}
// 若是 threadSwitch
// 1. 停止 timer 執行
// 2. 依 threadSwitch 狀態決定要執行或是停止 thread
else {
[self stopTimer];
timerSwitch.on = FALSE;
if (threadSwitch.on)
[self startThread];
else
[self stopThread];
}
}
169
UISwitch
iAd 與 IAP
170
iAd 與 IAP
 Simulator 不能測試 IAP
 需要購買 iOS Developer Program
 賺錢全靠他們倆
 建立 IAP 項目
 建立測試人員
 不然你要一直花自己的錢去測試 IAP 嗎?
171
iOS Developer Program
172
建立新的 App
 iTune Connect
 https://itunesconnect.apple.com
173
174
175
上架時間及價格
176
Version Information
177
178
App 資料
179
聯絡資訊
180
上傳 App 圖片
181
182
新增 IAP
新增 IAP
183
選擇單賣的方式
184
IAP 項目名稱及價格
185
設定 IAP 項目語言
186
新增測試人員
187
新增測試人員
188
新增測試人員
 Email 可以亂填沒關係,不會認證,只會當 ID 用
189
問題
 如何取得 IAP 項目資料?
 如何購買 IAP 項目?
 如何取得已購買項目?
 如何記録已購買項目?
190
iAd 廣告
192
193
設定按鈕Touch Up
Inside 事件處理
iAd 及 IAP framework
 加入 iAd.framework 及 StoreKit.framework
194
處理 IAP
195
項目 說明
取得 IAP 項目 透過 SKProductRequest 處理
設定 SKProductRequestDelegate
實作 productsRequest:didReceiveResponse:
啟動 SKProductRequest 物件
購買 IAP 項目 透過 [SKPaymentQueue defaultQueue] 處理
實作 paymentQueue:updatedTransactions:
設定 addTransactionObserver
[[SKPaymentQueue defaultQueue] addPayment:payment]
恢復 IAp 項目 透過 [SKPaymentQueue defaultQueue] 處理
設定 addTransactionObserver
實作 paymentQueue:updatedTransactions:
[[SKPaymentQueue defaultQueue]
restoreCompletedTransactions]
196
取得 IAP 項目清
單
讀取 User Default
更新 IAP 項目界面
啟動 App
處理
SKPaymentQueue
交易事件
購買
SKPaymentQueue
addPayment
記錄購買項目在
User Default
處理
SKPaymentQueue
交易事件
恢復購買
SKPaymentQueue
restoreCompletedTr
ansactions
記錄購買項目在
User Default
使用者設定檔
 使用 NSUserDefaults 類別
 儲存在
 <Application_Home>/Library/Preferences/<BundleId>.plist
 以 Key, Value 的方式儲存
 Value 資料型態
 Integer, float, double, string, dictionary, object
197
// 取得 NSUserDefault 物件
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
// 設定 (key, value)
[defaults setBool:FALSE forKey:@"key"];
// 依 key 取得 value
BOOL value = [defaults boolForKey:@"key"];
IADViewController.h
#import <UIKit/UIKit.h>
#import <iAd/iAd.h>
#import <StoreKit/StoreKit.h>
@interface IADViewController : UIViewController<SKProductsRequestDelegate,
SKPaymentTransactionObserver>
// IAP 項目 UI 介面元件
@property (weak, nonatomic) IBOutlet UILabel *productIdentifierLabel;
@property (weak, nonatomic) IBOutlet UILabel *productPriceLabel;
@property (weak, nonatomic) IBOutlet UILabel *productDescriptionLabel;
@property (weak, nonatomic) IBOutlet UILabel *productTitleLabel;
@property (weak, nonatomic) IBOutlet UILabel *purchaseStatus;
// 廣告
@property (weak, nonatomic) IBOutlet ADBannerView *adBanner;
// 按下購買
- (IBAction)pressPurchase:(id)sender;
// 按下恢復購買狀態
- (IBAction)pressRestore:(id)sender;
@end
198
IADViewController.m
- (void)viewDidLoad
{
[super viewDidLoad];
// 清除購買記錄
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
[defaults setBool:FALSE forKey:PRODUCT_IDENTIFIER];
// 更新購買狀態
[self updatePurchaseStatus];
// 下載 IAP Product
[self retrieveIapProduct];
// 註冊 SKPaymentQueue 觀察者
[[SKPaymentQueue defaultQueue] addTransactionObserver:self];
}
199
IADViewController.m
// 按下購買按鈕
- (IBAction)pressPurchase:(id)sender {
SKPayment * payment = [SKPayment paymentWithProduct:product];
[[SKPaymentQueue defaultQueue] addPayment:payment];
}
// 按下恢復購買按鈕
- (IBAction)pressRestore:(id)sender {
[[SKPaymentQueue defaultQueue] restoreCompletedTransactions];
}
// 設定 IAP 項目已購買
- (void)updateIAPPurchased:(NSString*)key
{
// 取得 UserDefault 物件
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
// 設定 com.comig.iphoneui.iap.noad 的值為 TRUE
[defaults setBool:TRUE forKey:key];
[self updatePurchaseStatus];
}
200
IADViewController.m
// 更新介面購買狀態
-(void)updatePurchaseStatus
{
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
if ([defaults boolForKey:PRODUCT_IDENTIFIER])
{
self.purchaseStatus.text = @"已購買";
// 隱藏廣告
self.adBanner.hidden = YES;
}
else
{
self.purchaseStatus.text = @"尚未購買";
// 顯示廣告
self.adBanner.hidden = NO;
}
}
201
IADViewController.m
// 開始下載 IAP 項目
-(void)retrieveIapProduct {
// 設定要下載的項目
_productIdentifiers = [NSSet setWithObjects:PRODUCT_IDENTIFIER, nil];
// 建立 SKProductRequest 物件
_productsRequest = [[SKProductsRequest alloc]
initWithProductIdentifiers:_productIdentifiers];
// 設定委派
_productsRequest.delegate = self;
[_productsRequest start];
}
// 處理下載 IAP 項目
- (void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse
*)response
{
// 取得第一個 IAP 項目, 注意, response.products 是陣列
product = [response.products objectAtIndex:0];
self.productTitleLabel.text = product.localizedTitle;
self.productIdentifierLabel.text = product.productIdentifier;
self.productPriceLabel.text = product.localizedPrice;
self.productDescriptionLabel.text = product.localizedDescription;
}
202
處理 SKPaymentQueue 交易事件
203
項目 說明
SKPaymentTransactionStatePurchasing 購買處理中
SKPaymentTransactionStatePurchased 購買成功
SKPaymentTransactionStateFailed 購買/恢復失敗
SKPaymentTransactionStateRestored 恢復成功
enum SKPaymentTransactionState
IADViewController.m
// 處理 SKPaymentQueue 交易事件
- (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions
{
for (SKPaymentTransaction * transaction in transactions) {
switch (transaction.transactionState)
{
case SKPaymentTransactionStatePurchased:
[self alertMessage:@"購買成功"];
[self updateIAPPurchased:transaction.payment.productIdentifier];
[[SKPaymentQueue defaultQueue] finishTransaction:transaction];
break;
case SKPaymentTransactionStateFailed:
[self alertMessage:@"購買失敗"];
[[SKPaymentQueue defaultQueue] finishTransaction:transaction];
break;
case SKPaymentTransactionStateRestored:
[self alertMessage:@"恢復已購買成功"];
[self updateIAPPurchased:transaction.payment.productIdentifier];
[[SKPaymentQueue defaultQueue] finishTransaction:transaction];
default:
break;
}
};
} 204
整合所有範例
205
統整 Story Board
206
Main
Tab Bar
Page
Control
Table Collection Clock iAd
版面設計
207
設定 StoryBoard ID
 View Controller 需要設定
StoryBoardID
 因為 Main StoryBoard 已經包了一
個 Navigation View Controller,
所以不能以 Navigation View
Controller 當開頭
208
ViewController.m
@interface ViewController ()
{
UIStoryboard *tabBarStoryboard;
UIStoryboard *pageControlStoryboard;
UIStoryboard *tableStoryboard;
UIStoryboard *touchStoryboard;
UIStoryboard *collectionStoryboard;
UIStoryboard *clockStoryboard;
UIStoryboard *iADStoryboard;
}
@end
@implementation ViewController
- (void)viewDidLoad
{
[super viewDidLoad];
// 建立 Story Bard 物件
tabBarStoryboard = [UIStoryboard storyboardWithName:@"TabBar" bundle:nil];
pageControlStoryboard = [UIStoryboard storyboardWithName:@"PageControl" bundle:nil];
tableStoryboard = [UIStoryboard storyboardWithName:@"Table" bundle:nil];
touchStoryboard = [UIStoryboard storyboardWithName:@"Touch" bundle:nil];
collectionStoryboard = [UIStoryboard storyboardWithName:@"Collection" bundle:nil];
clockStoryboard = [UIStoryboard storyboardWithName:@"Clock" bundle:nil];
iADStoryboard = [UIStoryboard storyboardWithName:@"IAD" bundle:nil];
}
209
ViewController.m
// 顯示 Tab Bar 範例
- (IBAction)pressTabBar:(id)sender {
UIViewController *vc = [tabBarStoryboard
instantiateViewControllerWithIdentifier:@"TabBarViewController"];
[self.navigationController pushViewController:vc animated:TRUE];
}
// 顯示 Page Control 範例
- (IBAction)pressPageControl:(id)sender {
UIViewController *vc = [pageControlStoryboard
instantiateViewControllerWithIdentifier:@"PageViewController"];
[self.navigationController pushViewController:vc animated:TRUE];
}
// 顯示 Table 範例
- (IBAction)pressTable:(id)sender {
UIViewController *vc = [tableStoryboard
instantiateViewControllerWithIdentifier:@"TableViewController"];
[self.navigationController pushViewController:vc animated:TRUE];
}
// 顯示 Touch 範例
- (IBAction)pressTouch:(id)sender {
UIViewController *vc = [touchStoryboard
instantiateViewControllerWithIdentifier:@"TapViewController"];
[self.navigationController pushViewController:vc animated:TRUE];
}
210
ViewController.m
// 顯示 Collection 範例
- (IBAction)pressCollection:(id)sender {
UIViewController *vc = [collectionStoryboard
instantiateViewControllerWithIdentifier:@"CollectionViewController"];
[self.navigationController pushViewController:vc animated:TRUE];
}
// 顯示 Timer 與 Thread 範例
- (IBAction)pressTimerAndThread:(id)sender {
UIViewController *vc = [clockStoryboard
instantiateViewControllerWithIdentifier:@"ClockViewController"];
[self.navigationController pushViewController:vc animated:TRUE];
}
// 顯示 iAd 與 IAP 範例
- (IBAction)pressIADAndIAP:(id)sender {
UIViewController *vc = [iADStoryboard
instantiateViewControllerWithIdentifier:@"IADViewController"];
[self.navigationController pushViewController:vc animated:TRUE];
}
211
謝謝
212

Contenu connexe

Tendances

YUI is Sexy - 使用 YUI 作為開發基礎
YUI is Sexy - 使用 YUI 作為開發基礎YUI is Sexy - 使用 YUI 作為開發基礎
YUI is Sexy - 使用 YUI 作為開發基礎Joseph Chiang
 
Spring入门纲要
Spring入门纲要Spring入门纲要
Spring入门纲要yiditushe
 
105-2 iOS程式設計(八)
105-2 iOS程式設計(八)105-2 iOS程式設計(八)
105-2 iOS程式設計(八)Hao Lee
 
YUI is Sexy (for JSDC.tw)
YUI is Sexy (for JSDC.tw)YUI is Sexy (for JSDC.tw)
YUI is Sexy (for JSDC.tw)Joseph Chiang
 
Myfox on NodeJS
Myfox on NodeJSMyfox on NodeJS
Myfox on NodeJSaleafs
 
15 Subclassing UITableViewCell
15 Subclassing UITableViewCell15 Subclassing UITableViewCell
15 Subclassing UITableViewCellTom Fan
 
用Jquery实现拖拽层
用Jquery实现拖拽层用Jquery实现拖拽层
用Jquery实现拖拽层yiditushe
 
Kissy模块化实践
Kissy模块化实践Kissy模块化实践
Kissy模块化实践yiming he
 
再接再勵學 Swift 程式設計
再接再勵學 Swift 程式設計再接再勵學 Swift 程式設計
再接再勵學 Swift 程式設計政斌 楊
 
J engine -构建高性能、可监控的前端应用框架
J engine -构建高性能、可监控的前端应用框架J engine -构建高性能、可监控的前端应用框架
J engine -构建高性能、可监控的前端应用框架fangdeng
 
iOS swift 接力使力之玩 open data
iOS swift 接力使力之玩 open dataiOS swift 接力使力之玩 open data
iOS swift 接力使力之玩 open data政斌 楊
 
J engine -构建高性能、可监控的前端应用框架
J engine -构建高性能、可监控的前端应用框架J engine -构建高性能、可监控的前端应用框架
J engine -构建高性能、可监控的前端应用框架wtxidian
 

Tendances (13)

YUI is Sexy - 使用 YUI 作為開發基礎
YUI is Sexy - 使用 YUI 作為開發基礎YUI is Sexy - 使用 YUI 作為開發基礎
YUI is Sexy - 使用 YUI 作為開發基礎
 
Spring入门纲要
Spring入门纲要Spring入门纲要
Spring入门纲要
 
105-2 iOS程式設計(八)
105-2 iOS程式設計(八)105-2 iOS程式設計(八)
105-2 iOS程式設計(八)
 
YUI is Sexy (for JSDC.tw)
YUI is Sexy (for JSDC.tw)YUI is Sexy (for JSDC.tw)
YUI is Sexy (for JSDC.tw)
 
Myfox on NodeJS
Myfox on NodeJSMyfox on NodeJS
Myfox on NodeJS
 
15 Subclassing UITableViewCell
15 Subclassing UITableViewCell15 Subclassing UITableViewCell
15 Subclassing UITableViewCell
 
用Jquery实现拖拽层
用Jquery实现拖拽层用Jquery实现拖拽层
用Jquery实现拖拽层
 
I os 14
I os 14I os 14
I os 14
 
Kissy模块化实践
Kissy模块化实践Kissy模块化实践
Kissy模块化实践
 
再接再勵學 Swift 程式設計
再接再勵學 Swift 程式設計再接再勵學 Swift 程式設計
再接再勵學 Swift 程式設計
 
J engine -构建高性能、可监控的前端应用框架
J engine -构建高性能、可监控的前端应用框架J engine -构建高性能、可监控的前端应用框架
J engine -构建高性能、可监控的前端应用框架
 
iOS swift 接力使力之玩 open data
iOS swift 接力使力之玩 open dataiOS swift 接力使力之玩 open data
iOS swift 接力使力之玩 open data
 
J engine -构建高性能、可监控的前端应用框架
J engine -构建高性能、可监控的前端应用框架J engine -构建高性能、可监控的前端应用框架
J engine -构建高性能、可监控的前端应用框架
 

En vedette

I os swift 3.0 初體驗 &amp; 玩 facebook sdk
I os swift 3.0 初體驗 &amp; 玩 facebook sdkI os swift 3.0 初體驗 &amp; 玩 facebook sdk
I os swift 3.0 初體驗 &amp; 玩 facebook sdk政斌 楊
 
Bash shell script 教學
Bash shell script 教學Bash shell script 教學
Bash shell script 教學Ming-Sian Lin
 
File system in iOS
File system in iOSFile system in iOS
File system in iOSPurvik Rana
 
MS SQL 2012 安裝與基本使用教學
MS SQL 2012 安裝與基本使用教學MS SQL 2012 安裝與基本使用教學
MS SQL 2012 安裝與基本使用教學Yuan Chiu
 
Windows server 2012 r2 active directory建置實務
Windows server 2012 r2 active directory建置實務Windows server 2012 r2 active directory建置實務
Windows server 2012 r2 active directory建置實務Sergio Io
 
淺入淺出 MySQL & PostgreSQL
淺入淺出 MySQL & PostgreSQL淺入淺出 MySQL & PostgreSQL
淺入淺出 MySQL & PostgreSQLYi-Feng Tzeng
 
Track B-3 解構大數據架構 - 大數據系統的伺服器與網路資源規劃
Track B-3 解構大數據架構 - 大數據系統的伺服器與網路資源規劃Track B-3 解構大數據架構 - 大數據系統的伺服器與網路資源規劃
Track B-3 解構大數據架構 - 大數據系統的伺服器與網路資源規劃Etu Solution
 
大數據的獲利模式
大數據的獲利模式大數據的獲利模式
大數據的獲利模式Chang Chiao Hui
 
iOS Release Management
iOS Release ManagementiOS Release Management
iOS Release ManagementSamuel Chow
 
Big Data Tornado - 2015 台灣 Big Data 企業經典應用案例分享
Big Data Tornado - 2015 台灣 Big Data 企業經典應用案例分享Big Data Tornado - 2015 台灣 Big Data 企業經典應用案例分享
Big Data Tornado - 2015 台灣 Big Data 企業經典應用案例分享Etu Solution
 
大數據運算媒體業案例分享 (Big Data Compute Case Sharing for Media Industry)
大數據運算媒體業案例分享 (Big Data Compute Case Sharing for Media Industry)大數據運算媒體業案例分享 (Big Data Compute Case Sharing for Media Industry)
大數據運算媒體業案例分享 (Big Data Compute Case Sharing for Media Industry)Amazon Web Services
 
Configuration As Code - Adoption of the Job DSL Plugin at Netflix
Configuration As Code - Adoption of the Job DSL Plugin at NetflixConfiguration As Code - Adoption of the Job DSL Plugin at Netflix
Configuration As Code - Adoption of the Job DSL Plugin at NetflixJustin Ryan
 

En vedette (14)

I os swift 3.0 初體驗 &amp; 玩 facebook sdk
I os swift 3.0 初體驗 &amp; 玩 facebook sdkI os swift 3.0 初體驗 &amp; 玩 facebook sdk
I os swift 3.0 初體驗 &amp; 玩 facebook sdk
 
iOS Views
iOS ViewsiOS Views
iOS Views
 
Bash shell script 教學
Bash shell script 教學Bash shell script 教學
Bash shell script 教學
 
File system in iOS
File system in iOSFile system in iOS
File system in iOS
 
MS SQL 2012 安裝與基本使用教學
MS SQL 2012 安裝與基本使用教學MS SQL 2012 安裝與基本使用教學
MS SQL 2012 安裝與基本使用教學
 
Windows server 2012 r2 active directory建置實務
Windows server 2012 r2 active directory建置實務Windows server 2012 r2 active directory建置實務
Windows server 2012 r2 active directory建置實務
 
淺入淺出 MySQL & PostgreSQL
淺入淺出 MySQL & PostgreSQL淺入淺出 MySQL & PostgreSQL
淺入淺出 MySQL & PostgreSQL
 
Track B-3 解構大數據架構 - 大數據系統的伺服器與網路資源規劃
Track B-3 解構大數據架構 - 大數據系統的伺服器與網路資源規劃Track B-3 解構大數據架構 - 大數據系統的伺服器與網路資源規劃
Track B-3 解構大數據架構 - 大數據系統的伺服器與網路資源規劃
 
大數據的獲利模式
大數據的獲利模式大數據的獲利模式
大數據的獲利模式
 
iOS Release Management
iOS Release ManagementiOS Release Management
iOS Release Management
 
大數據的基本概念(上)
大數據的基本概念(上)大數據的基本概念(上)
大數據的基本概念(上)
 
Big Data Tornado - 2015 台灣 Big Data 企業經典應用案例分享
Big Data Tornado - 2015 台灣 Big Data 企業經典應用案例分享Big Data Tornado - 2015 台灣 Big Data 企業經典應用案例分享
Big Data Tornado - 2015 台灣 Big Data 企業經典應用案例分享
 
大數據運算媒體業案例分享 (Big Data Compute Case Sharing for Media Industry)
大數據運算媒體業案例分享 (Big Data Compute Case Sharing for Media Industry)大數據運算媒體業案例分享 (Big Data Compute Case Sharing for Media Industry)
大數據運算媒體業案例分享 (Big Data Compute Case Sharing for Media Industry)
 
Configuration As Code - Adoption of the Job DSL Plugin at Netflix
Configuration As Code - Adoption of the Job DSL Plugin at NetflixConfiguration As Code - Adoption of the Job DSL Plugin at Netflix
Configuration As Code - Adoption of the Job DSL Plugin at Netflix
 

Similaire à iOS App 開發 -- Storybard 基礎練習、APP 上架、IAP

YUIconf2010介绍
YUIconf2010介绍YUIconf2010介绍
YUIconf2010介绍ling yu
 
10 Editing UITableView
10 Editing UITableView10 Editing UITableView
10 Editing UITableViewTom Fan
 
Spring 2.x 中文
Spring 2.x 中文Spring 2.x 中文
Spring 2.x 中文Guo Albert
 
09 UITableView and UITableViewController
09 UITableView and UITableViewController09 UITableView and UITableViewController
09 UITableView and UITableViewControllerTom Fan
 
旺铺前端设计和实现
旺铺前端设计和实现旺铺前端设计和实现
旺铺前端设计和实现hua qiu
 
Dive into kissy
Dive into kissyDive into kissy
Dive into kissyjay li
 
Android应用开发 - 沈大海
Android应用开发 - 沈大海Android应用开发 - 沈大海
Android应用开发 - 沈大海Shaoning Pan
 
掌星 移动互联网开发笔记-Vol001
掌星 移动互联网开发笔记-Vol001掌星 移动互联网开发笔记-Vol001
掌星 移动互联网开发笔记-Vol001rainx1982
 
Html5开发android应用程序概述
Html5开发android应用程序概述Html5开发android应用程序概述
Html5开发android应用程序概述kevin_yanggl
 
JdonFramework中文
JdonFramework中文JdonFramework中文
JdonFramework中文banq jdon
 
前端MVC之backbone
前端MVC之backbone前端MVC之backbone
前端MVC之backboneJerry Xie
 
透過 Windows Azure Mobile Services 開發各平台 Apps
透過 Windows Azure Mobile Services 開發各平台 Apps透過 Windows Azure Mobile Services 開發各平台 Apps
透過 Windows Azure Mobile Services 開發各平台 AppsEric ShangKuan
 
Introduction to CodeIgniter
Introduction to CodeIgniterIntroduction to CodeIgniter
Introduction to CodeIgniterChun-Kai Wang
 
Uliweb cheat sheet_0.1
Uliweb cheat sheet_0.1Uliweb cheat sheet_0.1
Uliweb cheat sheet_0.1modou li
 

Similaire à iOS App 開發 -- Storybard 基礎練習、APP 上架、IAP (20)

I os 10
I os 10I os 10
I os 10
 
005
005005
005
 
I os 16
I os 16I os 16
I os 16
 
YUIconf2010介绍
YUIconf2010介绍YUIconf2010介绍
YUIconf2010介绍
 
10 Editing UITableView
10 Editing UITableView10 Editing UITableView
10 Editing UITableView
 
Spring 2.x 中文
Spring 2.x 中文Spring 2.x 中文
Spring 2.x 中文
 
09 UITableView and UITableViewController
09 UITableView and UITableViewController09 UITableView and UITableViewController
09 UITableView and UITableViewController
 
I os 05
I os 05I os 05
I os 05
 
旺铺前端设计和实现
旺铺前端设计和实现旺铺前端设计和实现
旺铺前端设计和实现
 
Dive into kissy
Dive into kissyDive into kissy
Dive into kissy
 
Android应用开发 - 沈大海
Android应用开发 - 沈大海Android应用开发 - 沈大海
Android应用开发 - 沈大海
 
掌星 移动互联网开发笔记-Vol001
掌星 移动互联网开发笔记-Vol001掌星 移动互联网开发笔记-Vol001
掌星 移动互联网开发笔记-Vol001
 
Html5开发android应用程序概述
Html5开发android应用程序概述Html5开发android应用程序概述
Html5开发android应用程序概述
 
JdonFramework中文
JdonFramework中文JdonFramework中文
JdonFramework中文
 
前端MVC之backbone
前端MVC之backbone前端MVC之backbone
前端MVC之backbone
 
透過 Windows Azure Mobile Services 開發各平台 Apps
透過 Windows Azure Mobile Services 開發各平台 Apps透過 Windows Azure Mobile Services 開發各平台 Apps
透過 Windows Azure Mobile Services 開發各平台 Apps
 
I os 02
I os 02I os 02
I os 02
 
I os 01
I os 01I os 01
I os 01
 
Introduction to CodeIgniter
Introduction to CodeIgniterIntroduction to CodeIgniter
Introduction to CodeIgniter
 
Uliweb cheat sheet_0.1
Uliweb cheat sheet_0.1Uliweb cheat sheet_0.1
Uliweb cheat sheet_0.1
 

iOS App 開發 -- Storybard 基礎練習、APP 上架、IAP

  • 3. Who am I?  林銘賢  白天  網路公司工程師  晚上  南台講師  其它時間  導航 App Navier HUD iOS 版 作者
  • 8. 課程大綱  iOS App 基礎  Tab Bar  Page Control  Table  Gesture  Collection  Timer and Thread  iAd and IAP 8
  • 14. App 沙盒 (SandBox) 每個 App 都有自 獨立的目錄 14
  • 15. 常用目錄 15 目錄名稱 說明 <Application_Home>/<AppName>.app App bundle。不能在這個目錄寫 入任何資料 <Application_Home>/Documents 存放一般資料,不會被系統清除 <Application_Home>/Library 存放與 code 有關,但非使用者 資料的地方 <Application_Home>/tmp 暫存的目錄,會被系統清除
  • 20. 20
  • 22. 視窗架構 22 實際上是由 View Controller 負責提供 UIView 給 Windows
  • 26. 26
  • 27. 27
  • 28. 28
  • 30. 30
  • 31. 31
  • 33. Segue Segue Push 把目標 View Controller 加到 Navigation 堆疊最上層 Modal 顯示目標 View Controller Custom 由使用者自定訂義 View Controller 之間的轉換 33
  • 34. Segue Push 與 Modal 的比較  Push  較有結構,有 Navigation 堆疊幫忙回上一層 View Controller  有 Navigation Bar  需要有 Navigation View Controller  Modal  可任意帶出下一個 View Controller  需自行定義 View Controller 之間的關係 34
  • 36. 專案設定  Xcode 5.1  iPhone Retina 4 inch, 64 bit 36
  • 37. 37
  • 38. 設定 framework 加入 iAd.framework ( iAd 廣告) StoreKit.framework (In App Purchase, IAP, 內購) 38
  • 49. 49
  • 50. 50
  • 51. 新增 Tab Bar 的 View Controller 51 滑屬右鍵
  • 52. 新增 Tab Bar 的 View Controller 52
  • 53. 53
  • 57. 57
  • 58. 58
  • 59. 59
  • 60. 新增界面元件與 View Controller 的關係 60 選擇 Automatic, 讓 Xcode 自動幫 你列出相關的 View Controller
  • 61. 新增界面元件與 View Controller 的關係 61 建立 ScrollView Outlet 設定 ScrollView 的 delegate
  • 62. PageViewController.h @interface PageViewController : UIViewController<UIScrollViewDelegate> @property (weak, nonatomic) IBOutlet UIScrollView *scrollView; @property (weak, nonatomic) IBOutlet UIPageControl *pageControl; @end PageViewController.m @interface PageViewController () { NSArray* imageArray; } @end 62
  • 63. PageViewController.m - (void)viewDidLoad { [super viewDidLoad]; // 建立圖片陣列 imageArray = [[NSArray alloc] initWithObjects: @"wallpaper1.jpg", @"wallpaper2.jpg", @"wallpaper3.jpg", @"wallpaper4.jpg", @"wallpaper5.jpg", @"wallpaper6.jpg", @"wallpaper7.jpg", @"wallpaper8.jpg", nil]; 63
  • 64. // 依序把圖片放到 Scroll View 的相對應位置 for (int i = 0; i < [imageArray count]; i++) { // 計算圖片大小及原點位置 CGRect frame; frame.origin.x = self.scrollView.frame.size.width * i; frame.origin.y = 0; frame.size = self.scrollView.frame.size; // 建立 UIImageView 物件存放圖片 UIImageView *imageView = [[UIImageView alloc] initWithFrame:frame]; imageView.image = [UIImage imageNamed:[imageArray objectAtIndex:i]]; [self.scrollView addSubview:imageView]; } // 設定 Scroll View 的內容物大小 self.scrollView.contentSize=CGSizeMake(320*8, 460); // 設定 Scroll View 委派物件為自己 self.scrollView.delegate = self; } 64
  • 65. PageViewController.m // 處理捲動事件 - (void)scrollViewDidScroll:(UIScrollView *)scrollView { // 取得目前顯示頁面的坐標 CGFloat pageWidth = self.scrollView.frame.size.width; // 計算出頁碼 int page = floor((self.scrollView.contentOffset.x - pageWidth / 2) / pageWidth) + 1; // 設定頁碼 self.pageControl.currentPage = page; } 65 UIScrollViewDelegate
  • 67. 功能  Table 資料的顯示  顯示詳細內容  移動  刪除  Navigation Bar  跨 View Controller 之間的資料傳遞 67
  • 68. 68
  • 69. 69
  • 70. 70
  • 72. 72
  • 73. Table View  UITableViewDataSource  提供要顯示的資料  UITableViewDelegate  處理 TableView 上的操作及事件 73
  • 74. Table View  設定 dataSource 與 delegate 74
  • 75. 75
  • 78. 78
  • 79. 設定自訂的 View Controller 類別  MyTableViewController  繼承 UITableViewController 79
  • 82. MyTableViewController.m - (void)viewDidLoad { NSMutableArray *section; [super viewDidLoad]; titles = [[NSMutableArray alloc] initWithCapacity:3]; // 第一個 Section 台南 section = [NSMutableArray arrayWithObjects: @"東區", @"中西區", @"北區", @"南區", @"永康區", @"安平區", nil]; [titles addObject:section]; // 第二個 Section 高雄 section = [NSMutableArray arrayWithObjects:@"三民", @"前鎮", @"苓雅", nil]; [titles addObject:section]; // 第三個 Section 屏東 section = [NSMutableArray arrayWithObjects:@"三地門", @"恆春", nil]; [titles addObject:section]; // 設定 Navigation Bar 的右邊按鈕為 Table View 的編輯按鈕 self.navigationItem.rightBarButtonItem = self.editButtonItem; } 82
  • 83. MyTableViewController.m // 設定 Section 數 - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { return titles.count; } // 設定 Section 內的列數 - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { return ((NSMutableArray*)[titles objectAtIndex:section]).count; } // 設定要顯示 cell - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { // 透過 Identifier: DefaultCell 取得 Cell UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"DefaultCell" forIndexPath:indexPath]; // 設定區域名稱 cell.textLabel.text = [[titles objectAtIndex:indexPath.section] objectAtIndex:indexPath.row]; return cell; } 83 UITableViewDataSource UITableViewDataSource UITableViewDataSource
  • 84. MyTableViewController.m // 設定各 Section 的名稱 - (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section { NSString *sectionName; switch (section) { case 0: sectionName = @"台南"; break; case 1: sectionName = @"高雄"; break; default: sectionName = @"屏東"; break; } return sectionName; } 84 UITableViewDataSource
  • 86. MyTableViewController.m // 設定是否允許編輯列 (刪除) - (BOOL)tableView:(UITableView *)tableView canEditRowAtIndexPath:(NSIndexPath *)indexPath { return YES; } // 確定輯輯後狀態 - (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath { // 編輯的類型為『刪除』 if (editingStyle == UITableViewCellEditingStyleDelete) { // 更新 Section 資料 NSMutableArray *section; section = [titles objectAtIndex:indexPath.section]; [section removeObjectAtIndex:indexPath.row]; if (section.count == 0) { [titles removeObject:section]; } // 更新表格狀態 [tableView deleteRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationFade]; } 86 UITableViewDelegate UITableViewDelegate
  • 88. MyTableViewController.m // 設定是否允許移動列 - (BOOL)tableView:(UITableView *)tableView canMoveRowAtIndexPath:(NSIndexPath *)indexPath { return YES; } // 移動列範圍判斷 - (NSIndexPath *)tableView:(UITableView *)tableView targetIndexPathForMoveFromRowAtIndexPath:(NSIndexPath *)sourceIndexPath toProposedIndexPath:(NSIndexPath *)proposedDestinationIndexPath { // 不在同一個 Secion 的話,不允許移動 if( sourceIndexPath.section != proposedDestinationIndexPath.section ) return sourceIndexPath; else return proposedDestinationIndexPath; } 88 UITableViewDelegate UITableViewDelegate
  • 89. MyTableViewController.m // 移動列 - (void)tableView:(UITableView *)tableView moveRowAtIndexPath:(NSIndexPath *)fromIndexPath toIndexPath:(NSIndexPath *)toIndexPath { NSMutableArray* section; // 判斷是否在同 Secion 內移動 if (fromIndexPath.section == toIndexPath.section) { NSString *fromString; section = [titles objectAtIndex:fromIndexPath.section]; fromString = [section objectAtIndex:fromIndexPath.row]; // 把被移動的列從原本的 Secion 中刪除 [section removeObjectAtIndex:fromIndexPath.row]; // 把被移動的列加到新位置 [section insertObject:fromString atIndex:toIndexPath.row]; } } 89 UITableViewDelegate
  • 90. 處理資料選擇及 跨 View Controller 之間的資料 傳遞 90
  • 91. MyTableViewController.m // 處理按下列時的事件 - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { // 儲存該列的地名 selectedString = [[titles objectAtIndex:indexPath.section] objectAtIndex:indexPath.row]; // 執行 segue, 移動到 DetailViewController [self performSegueWithIdentifier:@"segueDetail" sender:self]; } 91 UITableViewDelegate
  • 92. MyTableViewController.m // 準備處理 Segue - (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender { // 判斷即將要執行的 Segue 是否為 segueDetail if ([[segue identifier] isEqualToString:@"segueDetail"]) { // 取得目標 ViewController DetailViewController* detailViewController = [segue destinationViewController]; // 設定要顯示的地名 detailViewController.text = selectedString; } } 92 Segue
  • 93. DetailViewController.h @interface DetailViewController : UIViewController @property (weak, nonatomic) IBOutlet UILabel *textLabel; @property (weak, nonatomic) NSString *text; @end 93 DetailViewController.m - (void)viewWillAppear:(BOOL)animated { // 設定標籤顯示地名 self.textLabel.text = self.text; }
  • 95. 手勢類型 95 Tap 點擊 Rotation 旋轉 Pinch 縮放
  • 96. 手勢類型  Swipe (掃動)  固定方向  上下左右  Pan (拖拉)  任何方向  上下左右  斜對角 96
  • 102. 102
  • 103. UIGestureRecognizerState 103 UIGestureRecognizerStateBegan 開始 UIGestureRecognizerStateChanged 改變中,連續型的才有 UIGestureRecognizerStateEnded 結束 UIGestureRecognizerStateCancelled 取消 UIGestureRecognizerStateFailed 失敗 UIGestureRecognizerStateRecognized 與UIGestureRecognizerStateEnded 相同
  • 104. 104
  • 107. Tap (點擊)  透過 Segue 直接連到 Swipe View Controller 107
  • 109. 連接 Tap 與 Swipe 109 加入 Bar Button Item 並建立 Segue
  • 112. 112
  • 114. Swipe (掃動) 114 右到左,透過 Segue 移動 左到右,透過Sent Action 處理
  • 115. SwipeViewController.h // 處理 Swipe Gesture 由左滑到右邊事件 - (IBAction)handSwipeLeftToRight:(id)sender { // pop 回上一層 view controller [self.navigationController popViewControllerAnimated:TRUE]; } 115
  • 117. Pan (拖拉) 117 由 Sent Action 處理,藉由 x 方向的移動速度,判斷左右
  • 118. PanViewController.h // 處理 Pan 手勢 - (IBAction)handlePan:(id)sender { UIPanGestureRecognizer *panGesture = (UIPanGestureRecognizer*)sender; // 判斷是否為 pan 手勢結束狀態 if (panGesture.state == UIGestureRecognizerStateEnded) { // 取得移動速度 CGPoint velocity = [panGesture velocityInView:self.view]; // x > 0 為 左向右滑動,pop 回上一層 view controller if(velocity.x > 0) { [self.navigationController popViewControllerAnimated:TRUE]; } // x < 為 右向左滑動, 執行 segueLongPress // push 下一層 view controller (Long Press) else { [self performSegueWithIdentifier:@"segueLongPress" sender:self]; } } } 118
  • 119. Long Press (長按)  長按切換按鈕顏色 119
  • 120. Long Press (長按) 120 由 Sent Action 處理
  • 121. LongViewController.h @interface LongPressViewController : UIViewController // 處理長按手勢 - (IBAction)handleLongPress:(id)sender; // 中央按鈕 @property (weak, nonatomic) IBOutlet UIButton *longPressButton; @end 121 LongViewController.m - (void)viewDidLoad { [super viewDidLoad]; // 設定角落的圓弧半徑為 60 self.longPressButton.layer.cornerRadius = 60; // 設定邊界線條寬度為 0,即隱藏邊界 self.longPressButton.layer.borderWidth = 0.0f; }
  • 122. LongViewController.m // 處理長按手勢 - (IBAction)handleLongPress:(id)sender { // 型態轉換 UILongPressGestureRecognizer *longPressGesture = (UILongPressGestureRecognizer*)sender; // 長按手勢開始 if (longPressGesture.state == UIGestureRecognizerStateBegan) { // 按鈕綠色變紅色 if ([self.longPressButton.backgroundColor isEqual:[UIColor greenColor]]) { self.longPressButton.backgroundColor = [UIColor redColor]; } // 按鈕紅色變綠色 else { self.longPressButton.backgroundColor = [UIColor greenColor]; } } } 122
  • 123. Pinch (縮放)  利用 Pinch 縮放圖片大小 123
  • 125. Pinch (縮放) 125 由 Sent Action 處理
  • 126. PinchViewController.m @interface PinchViewController () { // 記錄上一次放大的倍數 CGFloat lastScale; } @end 126
  • 127. PinchViewController.m // 處理 pinch 手勢 - (IBAction)handlePinch:(UIPinchGestureRecognizer *)recognizer { float scale; // 若為手勢剛開始狀態,設定 lastScale = 1.0 (無放大縮小) if (recognizer.state == UIGestureRecognizerStateBegan) { lastScale = 1.0; return; } // 其它狀態, 依 lastScale 的值來決定要放大多少 scale = 1 + (recognizer.scale-lastScale); lastScale = recognizer.scale; // 放大圖片 self.imageView.transform = CGAffineTransformScale(self.imageView.transform, scale, scale); } 127
  • 130. RotationViewController.m @interface RotationViewController () { // 記錄上次旋轉角度 CGFloat lastRotation; } @end 130
  • 131. RotationViewController.m // 處理 Rotation 手勢 - (IBAction)handleRotation:(UIRotationGestureRecognizer *)sender { // 若為手勢剛開始狀態,設定 lastRotation = 0.0 (無旋轉) if([sender state] == UIGestureRecognizerStateBegan) { lastRotation = 0.0; return; } // 其它狀態, 依 lastRotation 的值及來決定要旋轉幾度 CGFloat rotation = 0.0 - (lastRotation - [sender rotation]); // 計算新的 transform CGAffineTransform currentTransform = self.imageView.transform; CGAffineTransform newTransform = CGAffineTransformRotate(currentTransform,rotation); // 設定新的 transform [self.imageView setTransform:newTransform]; // 更新 lastRotation 下次使用 lastRotation = [sender rotation]; } 131
  • 132. 結合 Pan, Pinch, Rotation  可移動、縮放、旋轉圖片 132
  • 133. PinchRotationViewController.m @interface PinchRotationViewController : UIViewController // 處理 Rotation 手勢 - (IBAction)handleRotation:(UIRotationGestureRecognizer *)sender; // 處理 pinch 手勢 - (IBAction)handlePinch:(UIPinchGestureRecognizer *)sender; // 處理 Pan 手勢 - (IBAction)handlePan:(UIPanGestureRecognizer *)sender; // UIImageView, 圖片 UI 物件 @property (weak, nonatomic) IBOutlet UIImageView *imageView; @end 133
  • 134. PinchRotationViewController.m // 處理 pan 手勢 - (IBAction)handlePan:(UIPanGestureRecognizer *)sender { // 若是 pan 手勢進行中或結束時,更新圖片位置 if ((sender.state == UIGestureRecognizerStateChanged) || (sender.state == UIGestureRecognizerStateEnded)) { // 取得手勢位置 CGPoint location = [sender locationInView:self.view]; // 設定圖片中心 self.imageView.center = location; } } 134
  • 139. Collection View 139 勾選 Header 把 UIImageView 拉到 Cell
  • 140. 140
  • 141. 設定 Cell 及 Header 大小 141
  • 142. 嵌入 Navigation View Controller 142 Embedded View Controller
  • 143. 加入 Full Image View Controller 143
  • 144.  建立 Section Header 的類別 及識別  建立 Cell 的類別 及識別  建立 Full Image View Controller 類別 144
  • 145. Collection Reusable View  設定 Identifier 145
  • 146. Collection Reusable View  設定 Identifier 146
  • 147. Full Image View Controller UIImageView 設定 147
  • 148. MyCollectionViewCell.h @interface MyCollectionViewCell : UICollectionViewCell @property (strong, nonatomic) IBOutlet UIImageView *imageView; @end 148 MySupplementaryView.h @interface MySupplementaryView : UICollectionReusableView @property (weak, nonatomic) IBOutlet UILabel *headerLabel; @end
  • 149. FullImageViewController.h @interface FullImageViewController : UIViewController // 要顯示的 UIImageView @property (weak, nonatomic) IBOutlet UIImageView *imageView; // 暫存的圖片 @property (strong, nonatomic) UIImage *image; @end 149 FullImageViewController.m - (void)viewWillAppear:(BOOL)animated { // 設定 imageView 的圖片 self.imageView.image = self.image; }
  • 150. MyCollectionViewController.m #import "MySupplementaryView.h" #import "FullImageViewController.h" @interface MyCollectionViewController () { // 選擇的圖片 UIImage *selectedImage; // Secion 0 圖片名稱字串陣列 NSMutableArray *images0; // Secion 1 圖片名稱字串陣列 NSMutableArray *images1; } @end 150
  • 151. MyCollectionViewController.m - (void)viewDidLoad { [super viewDidLoad]; // 建立 2 個 Section 的圖片名稱字串陣列 images0 = [@[@"fish1.png", @"fish2.png", @"fish3.png",] mutableCopy]; images1 = [@[@"fish3.png", @"fish2.png", @"fish1.png",] mutableCopy]; } - (void)viewWillAppear:(BOOL)animated { // 隱藏 Navigation Bar [[self navigationController] setNavigationBarHidden:YES animated:YES]; } - (void)viewWillDisappear:(BOOL)animated { // 顯示 Navigation Bar [[self navigationController] setNavigationBarHidden:NO animated:YES]; } 151
  • 152. MyCollectionViewController.m // 回傳 Section 個數, 2 個 Section - (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView { return 2; } // 回傳 Section 裡的項目個數 - (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section { if (section == 0) return images0.count; return images1.count; } 152 UICollectionViewDataSource UICollectionViewDataSource
  • 153. MyCollectionViewController.m // 設定各 Section 的名稱 -(UICollectionReusableView *)collectionView:(UICollectionView *)collectionView viewForSupplementaryElementOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath { MySupplementaryView *header = nil; // 判斷是 header 還是 footer if ([kind isEqual:UICollectionElementKindSectionHeader]) { // 取得 header header = [collectionView dequeueReusableSupplementaryViewOfKind:kind withReuseIdentifier:@"MyHeader" forIndexPath:indexPath]; // 設定標題 if (indexPath.section == 0) header.headerLabel.text = @"Fish 0 Gallery"; else header.headerLabel.text = @"Fish 1 Gallery"; } return header; } 153 UICollectionViewDataSource
  • 154. MyCollectionViewController.m // 設定要顯示 cell - (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath { UIImage *image; // 取得 cell MyCollectionViewCell *myCell = [collectionView dequeueReusableCellWithReuseIdentifier:@"MyCell" forIndexPath:indexPath]; // 依 cell 位置,建立圖片 if (indexPath.section == 0) image = [UIImage imageNamed:images0[indexPath.row]]; else image = [UIImage imageNamed:images1[indexPath.row]]; // 設定 cell 要顯示的圖片 myCell.imageView.image = image; return myCell; } 154 UICollectionViewDataSource
  • 155. MyCollectionViewController.m // 處理選擇的項目 - (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath; { // 取得選擇的圖片 if (indexPath.section == 0) selectedImage = [UIImage imageNamed:images0[indexPath.row]]; else selectedImage = [UIImage imageNamed:images1[indexPath.row]]; // 執行 Segue: pushShowFullImage [self performSegueWithIdentifier: @"pushShowFullImage" sender: self]; } // 準備執行 Segue - (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender { // 判斷要執行的 Segue 是否為 pushShowFullImage if ([[segue identifier] isEqualToString:@"pushShowFullImage"]) { // 取得目標 View Controller FullImageViewController *fvc = [segue destinationViewController]; // 設定 FullImageViewController 要顯示的圖片 fvc.image = selectedImage; } } 155 UICollectionViewDelegate
  • 157. Clock  使用 NSTimer 及 NSThread 實作時鐘  利用 UISwitch 切換 開關  使用 PickerView 選 擇時間格式 157
  • 158. 158 設定 UISwitch 的 Value Changed 事件
  • 159. 159 設定 Picker View 的 delegate 及 datasource
  • 160. NSThread 限制  透過NSThread建立出來的執行緒與控制界面的主執行緒 是不一樣的  NSTimer 建立出來的計時器可以變更界面內容  NSThread 逼立出來的執行緒不能變更界面內容  需要回到主執行緒才能變更  performSelectorOnMainThread:withObject:waitUntilDone:  performSelectorOnMainThread:withObject:waitUntilDone:mo des: 160
  • 161. 取得介面元件的另一個方法  介面元件都有一個 Tag  不一定要透過 Story Board 連接 View Controller 與介面 元件之間的關係  View Controller 可以透過 Tag 在程式執行期間,動態取 得介面元件 161 [self.view viewWithTag:1]
  • 163. ClockViewController.m @interface ClockViewController () { // UI 界面元件 UILabel *clockLabel; UISwitch *timerSwitch; UISwitch *threadSwitch; // 儲存 date format 字串的陣列 NSArray *dateFormat; // 計時器 NSTimer *clockTimer; // 執行緒 NSThread *clockThread; // 執行緒生命判斷標籤 BOOL isThreadAlive; // 目前使用的 date format NSDateFormatter *dateFormatter; } @end 163
  • 164. ClockViewController.m - (void)viewDidLoad { [super viewDidLoad]; clockTimer = nil; clockThread = nil; isThreadAlive = FALSE; // 透過 tag 取得 UI 介面元件 clockLabel = (UILabel*)[self.view viewWithTag:1]; timerSwitch = (UISwitch*)[self.view viewWithTag:2]; threadSwitch = (UISwitch*)[self.view viewWithTag:3]; // 設定 dateFormat 陣列 dateFormat = [NSArray arrayWithObjects:@"HH:mm:ss", @"ss", @"yyyy-MM-dd HH:mm:ss", @"MM/dd HH:mm:ss", nil]; // 設定預設 date format 為 dateFormat 字串陣列裡的第 0 個 dateFormatter = [[NSDateFormatter alloc] init]; [dateFormatter setDateFormat:[dateFormat objectAtIndex:0]]; } 164
  • 165. ClockViewController.m // 設定 PickerView 的元件個數 - (NSInteger)numberOfComponentsInPickerView:(UIPickerView *)pickerView { return 1; } // 設定 PickerView 元件裡的列數 - (NSInteger)pickerView:(UIPickerView *)pickerView numberOfRowsInComponent:(NSInteger)component { return dateFormat.count; } // 設定 PickerView 列要顯示的字串 - (NSString *)pickerView:(UIPickerView *)pickerView titleForRow:(NSInteger)row forComponent:(NSInteger)component { return [dateFormat objectAtIndex:row]; } // 處理選擇 PickerViw 列的事件 - (void)pickerView:(UIPickerView *)pickerView didSelectRow:(NSInteger)row inComponent:(NSInteger)component { // 設定新的 date format [dateFormatter setDateFormat:[dateFormat objectAtIndex:row]]; } 165 UIPickerViewDataSource UIPickerViewDataSource UIPickerViewDataSource UIPickerViewDelegate
  • 166. ClockViewController.m // 啟動 Timer - (void)startTimer { if (clockTimer == nil) { clockTimer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(clockTimeout) userInfo:nil repeats:YES]; } } // 停止 Timer - (void)stopTimer { if (clockTimer != nil) { [clockTimer invalidate]; clockTimer = nil; } } // 處理 timeout 事件, 更新要顯示的時間 - (void)clockTimeout { // 依目前的 dateFormatter 格式顯示時間 clockLabel.text = [dateFormatter stringFromDate:[NSDate date]]; } 166
  • 167. ClockViewController.m // 啟動執行緒 - (void)startThread { if (clockThread == nil) { clockThread = [[NSThread alloc] initWithTarget:self selector:@selector(threadMain) object:nil]; isThreadAlive = TRUE; [clockThread start]; // Actually create the thread } } // 停止執行緒 - (void)stopThread { if (clockThread != nil) { clockThread = nil; isThreadAlive = FALSE; } } 167
  • 168. ClockViewController.m // 執行緒本體 - (void)threadMain { while(isThreadAlive) { // 通知 Main Thread 執行 clockTimeout [self performSelectorOnMainThread:@selector(clockTimeout) withObject:nil waitUntilDone:FALSE]; // 睡 1 秒 sleep(1); } } 168
  • 169. ClockViewController.m // 處理 UISwitch value changed 事件 - (IBAction)handleSwitchValueChanged:(id)sender { // 若是 timerSwitch // 1. 停止 thread 執行 // 2. 依 timerSwitch 狀態決定要執行或是停止 timer if (sender == timerSwitch) { [self stopThread]; threadSwitch.on = FALSE; if (timerSwitch.on) [self startTimer]; else [self stopTimer]; } // 若是 threadSwitch // 1. 停止 timer 執行 // 2. 依 threadSwitch 狀態決定要執行或是停止 thread else { [self stopTimer]; timerSwitch.on = FALSE; if (threadSwitch.on) [self startThread]; else [self stopThread]; } } 169 UISwitch
  • 171. iAd 與 IAP  Simulator 不能測試 IAP  需要購買 iOS Developer Program  賺錢全靠他們倆  建立 IAP 項目  建立測試人員  不然你要一直花自己的錢去測試 IAP 嗎? 171
  • 173. 建立新的 App  iTune Connect  https://itunesconnect.apple.com 173
  • 174. 174
  • 175. 175
  • 178. 178
  • 190. 問題  如何取得 IAP 項目資料?  如何購買 IAP 項目?  如何取得已購買項目?  如何記録已購買項目? 190
  • 192. 192
  • 194. iAd 及 IAP framework  加入 iAd.framework 及 StoreKit.framework 194
  • 195. 處理 IAP 195 項目 說明 取得 IAP 項目 透過 SKProductRequest 處理 設定 SKProductRequestDelegate 實作 productsRequest:didReceiveResponse: 啟動 SKProductRequest 物件 購買 IAP 項目 透過 [SKPaymentQueue defaultQueue] 處理 實作 paymentQueue:updatedTransactions: 設定 addTransactionObserver [[SKPaymentQueue defaultQueue] addPayment:payment] 恢復 IAp 項目 透過 [SKPaymentQueue defaultQueue] 處理 設定 addTransactionObserver 實作 paymentQueue:updatedTransactions: [[SKPaymentQueue defaultQueue] restoreCompletedTransactions]
  • 196. 196 取得 IAP 項目清 單 讀取 User Default 更新 IAP 項目界面 啟動 App 處理 SKPaymentQueue 交易事件 購買 SKPaymentQueue addPayment 記錄購買項目在 User Default 處理 SKPaymentQueue 交易事件 恢復購買 SKPaymentQueue restoreCompletedTr ansactions 記錄購買項目在 User Default
  • 197. 使用者設定檔  使用 NSUserDefaults 類別  儲存在  <Application_Home>/Library/Preferences/<BundleId>.plist  以 Key, Value 的方式儲存  Value 資料型態  Integer, float, double, string, dictionary, object 197 // 取得 NSUserDefault 物件 NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults]; // 設定 (key, value) [defaults setBool:FALSE forKey:@"key"]; // 依 key 取得 value BOOL value = [defaults boolForKey:@"key"];
  • 198. IADViewController.h #import <UIKit/UIKit.h> #import <iAd/iAd.h> #import <StoreKit/StoreKit.h> @interface IADViewController : UIViewController<SKProductsRequestDelegate, SKPaymentTransactionObserver> // IAP 項目 UI 介面元件 @property (weak, nonatomic) IBOutlet UILabel *productIdentifierLabel; @property (weak, nonatomic) IBOutlet UILabel *productPriceLabel; @property (weak, nonatomic) IBOutlet UILabel *productDescriptionLabel; @property (weak, nonatomic) IBOutlet UILabel *productTitleLabel; @property (weak, nonatomic) IBOutlet UILabel *purchaseStatus; // 廣告 @property (weak, nonatomic) IBOutlet ADBannerView *adBanner; // 按下購買 - (IBAction)pressPurchase:(id)sender; // 按下恢復購買狀態 - (IBAction)pressRestore:(id)sender; @end 198
  • 199. IADViewController.m - (void)viewDidLoad { [super viewDidLoad]; // 清除購買記錄 NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults]; [defaults setBool:FALSE forKey:PRODUCT_IDENTIFIER]; // 更新購買狀態 [self updatePurchaseStatus]; // 下載 IAP Product [self retrieveIapProduct]; // 註冊 SKPaymentQueue 觀察者 [[SKPaymentQueue defaultQueue] addTransactionObserver:self]; } 199
  • 200. IADViewController.m // 按下購買按鈕 - (IBAction)pressPurchase:(id)sender { SKPayment * payment = [SKPayment paymentWithProduct:product]; [[SKPaymentQueue defaultQueue] addPayment:payment]; } // 按下恢復購買按鈕 - (IBAction)pressRestore:(id)sender { [[SKPaymentQueue defaultQueue] restoreCompletedTransactions]; } // 設定 IAP 項目已購買 - (void)updateIAPPurchased:(NSString*)key { // 取得 UserDefault 物件 NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults]; // 設定 com.comig.iphoneui.iap.noad 的值為 TRUE [defaults setBool:TRUE forKey:key]; [self updatePurchaseStatus]; } 200
  • 201. IADViewController.m // 更新介面購買狀態 -(void)updatePurchaseStatus { NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults]; if ([defaults boolForKey:PRODUCT_IDENTIFIER]) { self.purchaseStatus.text = @"已購買"; // 隱藏廣告 self.adBanner.hidden = YES; } else { self.purchaseStatus.text = @"尚未購買"; // 顯示廣告 self.adBanner.hidden = NO; } } 201
  • 202. IADViewController.m // 開始下載 IAP 項目 -(void)retrieveIapProduct { // 設定要下載的項目 _productIdentifiers = [NSSet setWithObjects:PRODUCT_IDENTIFIER, nil]; // 建立 SKProductRequest 物件 _productsRequest = [[SKProductsRequest alloc] initWithProductIdentifiers:_productIdentifiers]; // 設定委派 _productsRequest.delegate = self; [_productsRequest start]; } // 處理下載 IAP 項目 - (void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response { // 取得第一個 IAP 項目, 注意, response.products 是陣列 product = [response.products objectAtIndex:0]; self.productTitleLabel.text = product.localizedTitle; self.productIdentifierLabel.text = product.productIdentifier; self.productPriceLabel.text = product.localizedPrice; self.productDescriptionLabel.text = product.localizedDescription; } 202
  • 203. 處理 SKPaymentQueue 交易事件 203 項目 說明 SKPaymentTransactionStatePurchasing 購買處理中 SKPaymentTransactionStatePurchased 購買成功 SKPaymentTransactionStateFailed 購買/恢復失敗 SKPaymentTransactionStateRestored 恢復成功 enum SKPaymentTransactionState
  • 204. IADViewController.m // 處理 SKPaymentQueue 交易事件 - (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions { for (SKPaymentTransaction * transaction in transactions) { switch (transaction.transactionState) { case SKPaymentTransactionStatePurchased: [self alertMessage:@"購買成功"]; [self updateIAPPurchased:transaction.payment.productIdentifier]; [[SKPaymentQueue defaultQueue] finishTransaction:transaction]; break; case SKPaymentTransactionStateFailed: [self alertMessage:@"購買失敗"]; [[SKPaymentQueue defaultQueue] finishTransaction:transaction]; break; case SKPaymentTransactionStateRestored: [self alertMessage:@"恢復已購買成功"]; [self updateIAPPurchased:transaction.payment.productIdentifier]; [[SKPaymentQueue defaultQueue] finishTransaction:transaction]; default: break; } }; } 204
  • 206. 統整 Story Board 206 Main Tab Bar Page Control Table Collection Clock iAd
  • 208. 設定 StoryBoard ID  View Controller 需要設定 StoryBoardID  因為 Main StoryBoard 已經包了一 個 Navigation View Controller, 所以不能以 Navigation View Controller 當開頭 208
  • 209. ViewController.m @interface ViewController () { UIStoryboard *tabBarStoryboard; UIStoryboard *pageControlStoryboard; UIStoryboard *tableStoryboard; UIStoryboard *touchStoryboard; UIStoryboard *collectionStoryboard; UIStoryboard *clockStoryboard; UIStoryboard *iADStoryboard; } @end @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; // 建立 Story Bard 物件 tabBarStoryboard = [UIStoryboard storyboardWithName:@"TabBar" bundle:nil]; pageControlStoryboard = [UIStoryboard storyboardWithName:@"PageControl" bundle:nil]; tableStoryboard = [UIStoryboard storyboardWithName:@"Table" bundle:nil]; touchStoryboard = [UIStoryboard storyboardWithName:@"Touch" bundle:nil]; collectionStoryboard = [UIStoryboard storyboardWithName:@"Collection" bundle:nil]; clockStoryboard = [UIStoryboard storyboardWithName:@"Clock" bundle:nil]; iADStoryboard = [UIStoryboard storyboardWithName:@"IAD" bundle:nil]; } 209
  • 210. ViewController.m // 顯示 Tab Bar 範例 - (IBAction)pressTabBar:(id)sender { UIViewController *vc = [tabBarStoryboard instantiateViewControllerWithIdentifier:@"TabBarViewController"]; [self.navigationController pushViewController:vc animated:TRUE]; } // 顯示 Page Control 範例 - (IBAction)pressPageControl:(id)sender { UIViewController *vc = [pageControlStoryboard instantiateViewControllerWithIdentifier:@"PageViewController"]; [self.navigationController pushViewController:vc animated:TRUE]; } // 顯示 Table 範例 - (IBAction)pressTable:(id)sender { UIViewController *vc = [tableStoryboard instantiateViewControllerWithIdentifier:@"TableViewController"]; [self.navigationController pushViewController:vc animated:TRUE]; } // 顯示 Touch 範例 - (IBAction)pressTouch:(id)sender { UIViewController *vc = [touchStoryboard instantiateViewControllerWithIdentifier:@"TapViewController"]; [self.navigationController pushViewController:vc animated:TRUE]; } 210
  • 211. ViewController.m // 顯示 Collection 範例 - (IBAction)pressCollection:(id)sender { UIViewController *vc = [collectionStoryboard instantiateViewControllerWithIdentifier:@"CollectionViewController"]; [self.navigationController pushViewController:vc animated:TRUE]; } // 顯示 Timer 與 Thread 範例 - (IBAction)pressTimerAndThread:(id)sender { UIViewController *vc = [clockStoryboard instantiateViewControllerWithIdentifier:@"ClockViewController"]; [self.navigationController pushViewController:vc animated:TRUE]; } // 顯示 iAd 與 IAP 範例 - (IBAction)pressIADAndIAP:(id)sender { UIViewController *vc = [iADStoryboard instantiateViewControllerWithIdentifier:@"IADViewController"]; [self.navigationController pushViewController:vc animated:TRUE]; } 211