ios app 開發 -- storybard 基礎練習、app 上架、iap

Post on 10-May-2015

2.319 Views

Category:

Software

0 Downloads

Preview:

Click to see full reader

DESCRIPTION

iOS App 開發 -- Storybard 基礎練習 包含以下主題 1. Tab page 2. Page Control 3. Table 4. Gesture 5. Collection 6. Timer 跟 Thread 7. iAd 跟 IAP

TRANSCRIPT

iOS App Development

1

2

資料下載

https://dl.dropboxusercontent.com/u/14692540/iphoneui.zip

Who am I?林銘賢

白天網路公司工程師

晚上南台講師

其它時間導航 App Navier HUD iOS 版 作者

Navier HUD iOS

Navier HUD iOS

Navier HUD iOS

Navier HUD iOS

8

課程大綱 iOS App 基礎

Tab Bar

Page Control

Table

Gesture

Collection

Timer and Thread

iAd and IAP

9

iOS App Basics

10

View 階層架構

11

View 階層架構

12

畫面坐標原點在左上角

13

Frame 與 Bounds

14

App 沙盒 (SandBox)

每個 App 都有自獨立的目錄

15

常用目錄

目錄名稱 說明<Application_Home>/<AppName>.app App bundle 。不能在這個目錄

寫入任何資料<Application_Home>/Documents 存放一般資料,不會被系統清除<Application_Home>/Library 存放與 code 有關,但非使用

者資料的地方<Application_Home>/tmp 暫存的目錄,會被系統清除

16

轉向直立

直立上下顛倒

橫向 - 左 橫向 - 右

17

啟動 APP

18

切換 App 執行進入背景模式

19

從背景模式切換到前景模式

20

21

視窗架構

22

視窗架構

實際上是由 View Controller 負責提供 UIView 給 Windows

23

init

loadView

viewDidLoad

viewWillAppear

viewDidAppear

viewWillDisappear

viewDidDisappear

viewDidUnload

dealloc

View Controller 的生命週期

24

View Controller 架構

25

View Controller 架構

26

27

28

29

Navigation

Push

Pop

Push

Pop

30

31

32

Modal

33

Segue

Segue

Push 把目標 View Controller 加到 Navigation 堆疊最上層

Modal 顯示目標 View Controller

Custom 由使用者自定訂義 View Controller 之間的轉換

34

Segue Push 與 Modal 的比較

Push較有結構,有 Navigation 堆疊幫忙回上一層 View

Controller有 Navigation Bar需要有 Navigation View Controller

Modal可任意帶出下一個 View Controller需自行定義 View Controller 之間的關係

35

專案設定

36

專案設定Xcode 5.1

iPhone Retina 4 inch, 64 bit

37

38

設定 framework加入

iAd.framework ( iAd 廣告 )

StoreKit.framework (In App Purchase, IAP, 內購 )

39

專案資料夾

40

變更預設 StoryBorad

41

設定 App Icon

42

設定 App 圖示與起始畫面

圖示

起始畫面

43

設定 App 圖示與起始畫面

44

設定起始畫面

45

Tab Bar

46

Tab Bar 範例

47

Tab Bar 架構

48

Tab Bar 設計

49

50

51

新增 Tab Bar 的 View Controller

滑屬右鍵

52

新增 Tab Bar 的 View Controller

53

54

Page Control

55

Page Control

56

使用到的元件

57

58

59

60

新增界面元件與 View Controller 的關係

選擇 Automatic, 讓 Xcode 自動幫你列出相關的 View Controller

61

新增界面元件與 View Controller 的關係

建立 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

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];

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;

}

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;

}

UIScrollViewDelegate

66

Table

67

功能Table 資料的顯示

顯示詳細內容

移動

刪除

Navigation Bar

跨 View Controller 之間的資料傳遞

68

69

70

71

嵌入 Navigation Controller

72

73

Table ViewUITableViewDataSource

提供要顯示的資料

UITableViewDelegate處理 TableView 上的操作及事件

74

Table View設定 dataSource 與 delegate

75

76

新增 Push Segue

77

新增 Push Segue

78

79

設定自訂的 View Controller 類別

MyTableViewController繼承 UITableViewController

80

MyTableViewController.m

@interface MyTableViewController ()

{

NSMutableArray *titles;

NSString *selectedString;

}

@end

81

準備 Table 資料

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;

}

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;

}

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;

}

UITableViewDataSource

85

處理資料編輯 ( 刪除 )

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];

}

}

UITableViewDelegate

UITableViewDelegate

87

處理資料移動

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;

}

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];

}

}

UITableViewDelegate

90

處理資料選擇及跨 View Controller 之間的

資料傳遞

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];

}

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;

}

}

Segue

93

DetailViewController.h

@interface DetailViewController : UIViewController

@property (weak, nonatomic) IBOutlet UILabel *textLabel;

@property (weak, nonatomic) NSString *text;

@end

DetailViewController.m

- (void)viewWillAppear:(BOOL)animated

{

// 設定標籤顯示地名 self.textLabel.text = self.text;

}

94

Gesture手勢處理

95

手勢類型Tap 點擊 Rotation 旋轉 Pinch 縮放

96

手勢類型Swipe (掃動 )

固定方向上下左右

Pan (拖拉 )任何方向上下左右斜對角

97

處理手勢的類別

98

元件

99

Tap 點擊不連續手勢類型

100

狀態轉換

101

Pinch 縮放連續手勢類型

102

103

UIGestureRecognizerState

UIGestureRecognizerStateBegan 開始UIGestureRecognizerStateChanged 改變中,連續型的才有UIGestureRecognizerStateEnded 結束UIGestureRecognizerStateCancelled 取消UIGestureRecognizerStateFailed 失敗UIGestureRecognizerStateRecognized

與 UIGestureRecognizerStateEnded 相同

104

105

取消 Auto Layout取消 Auto Layout

106

Tap ( 點擊 )按一下跳至下一層

點擊次數 手指數

107

Tap ( 點擊 )透過 Segue 直接連到 Swipe View Controller

108

Swipe (掃動 )

透過左右滑動回到上一層或下一層

109

連接 Tap 與 Swipe

加入 Bar Button Item 並建立 Segue

110

選擇 Push

111

建立的 Segue

112

113

Bar Item

設定名稱

114

Swipe (掃動 )

右到左,透過 Segue 移動 左到右,透過 Sent Action 處理

115

SwipeViewController.h

// 處理 Swipe Gesture 由左滑到右邊事件- (IBAction)handSwipeLeftToRight:(id)sender {

// pop 回上一層 view controller [self.navigationController popViewControllerAnimated:TRUE];

}

116

Pan (拖拉 )

透過左右滑動回到上一層或下一層

117

Pan (拖拉 )

由 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];

}

}

}

119

Long Press (長按 )長按切換按鈕顏色

120

Long Press (長按 )

由 Sent Action 處理

121

LongViewController.h@interface LongPressViewController : UIViewController

// 處理長按手勢- (IBAction)handleLongPress:(id)sender;

// 中央按鈕@property (weak, nonatomic) IBOutlet UIButton *longPressButton;

@end

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];

}

}

}

123

Pinch (縮放 )利用 Pinch 縮放圖片大

124

UIImageView

125

Pinch (縮放 )

由 Sent Action 處理

126

PinchViewController.m

@interface PinchViewController ()

{

// 記錄上一次放大的倍數 CGFloat lastScale;

}

@end

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);

}

128

Rotate (旋轉 ) 旋轉圖片

129

Rotate (旋轉 )

由 Sent Action 處理

130

RotationViewController.m

@interface RotationViewController ()

{

// 記錄上次旋轉角度 CGFloat lastRotation;

}

@end

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];

}

132

結合 Pan, Pinch, Rotation

可移動、縮放、旋轉圖片

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

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;

}

}

135

Collection

136

UICollectionViewController

137

Collection 設計

138

Collection Story Board

139

Collection View

勾選 Header把 UIImageView拉到 Cell

140

141

設定 Cell 及 Header 大小

142

嵌入 Navigation View Controller

Embedded View Controller

143

加入 Full Image View Controller

144

建立 Section Header 的類別及識別

建立 Cell 的類別及識別

建立 Full Image View Controller 類別

145

Collection Reusable View

設定 Identifier

146

Collection Reusable View

設定 Identifier

147

Full Image View Controller UIImageView 設定

148

MyCollectionViewCell.h

@interface MyCollectionViewCell : UICollectionViewCell

@property (strong, nonatomic) IBOutlet UIImageView *imageView;

@end

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

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

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];

}

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;

}

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;

}

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;

}

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;

}

}

UICollectionViewDelegate

156

Timer and Thread

157

Clock

使用 NSTimer 及 NSThread 實作時鐘

利用 UISwitch 切換開關

使用 PickerView 選擇時間格式

158

設定 UISwitch 的 Value Changed 事件

159

設定 Picker View 的 delegate 及 datasource

160

NSThread 限制透過 NSThread 建立出來的執行緒與控制界面的主執行緒是不一樣的

NSTimer 建立出來的計時器可以變更界面內容

NSThread 逼立出來的執行緒不能變更界面內容需要回到主執行緒才能變更

performSelectorOnMainThread:withObject:waitUntilDone:

performSelectorOnMainThread:withObject:waitUntilDone:modes:

161

取得介面元件的另一個方法介面元件都有一個 Tag

不一定要透過 Story Board 連接 View Controller 與介面元件之間的關係

View Controller 可以透過 Tag 在程式執行期間,動態取得介面元件

[self.view viewWithTag:1]

162

設定 Tag

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

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]];

}

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]];

}

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]];

}

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;

}

}

168

ClockViewController.m

// 執行緒本體- (void)threadMain

{

while(isThreadAlive)

{

// 通知 Main Thread 執行 clockTimeout [self performSelectorOnMainThread:@selector(clockTimeout) withObject:nil waitUntilDone:FALSE];

// 睡 1 秒 sleep(1);

}

}

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]; }}

UISwitch

170

iAd 與 IAP

171

iAd 與 IAPSimulator 不能測試 IAP

需要購買 iOS Developer Program

賺錢全靠他們倆

建立 IAP 項目

建立測試人員不然你要一直花自己的錢去測試 IAP 嗎?

172

iOS Developer Program

173

建立新的 App

iTune Connecthttps://itunesconnect.apple.com

174

175

176

上架時間及價格

177

Version Information

178

179

App 資料

180

聯絡資訊

181

上傳 App 圖片

182

新增 IAP

183

新增 IAP

184

選擇單賣的方式

185

IAP 項目名稱及價格

186

設定 IAP 項目語言

187

新增測試人員

188

新增測試人員

189

新增測試人員Email 可以亂填沒關係,不會認證,只會當 ID 用

190

問題如何取得 IAP 項目資料?

如何購買 IAP 項目?

如何取得已購買項目?

如何記録已購買項目?

iAd 廣告

192

193

設定按鈕 Touch Up Inside 事件處理

194

iAd 及 IAP framework加入 iAd.framework 及 StoreKit.framework

195

處理 IAP項目 說明

取得 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 restoreCompleted

Transactions

記錄購買項目在 User Default

197

使用者設定檔 使用 NSUserDefaults 類別 儲存在

<Application_Home>/Library/Preferences/<BundleId>.plist 以 Key, Value 的方式儲存 Value 資料型態

Integer, float, double, string, dictionary, object

// 取得 NSUserDefault 物件NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];

// 設定 (key, value)[defaults setBool:FALSE forKey:@"key"];

// 依 key 取得 valueBOOL 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

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];

}

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];

}

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;

}

}

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;

}

203

處理 SKPaymentQueue 交易事件

項目 說明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;

}

};

}

205

整合所有範例

206

統整 Story Board

Main

Tab BarPage

ControlTable

Collection

Clock iAd

207

版面設計

208

設定 StoryBoard ID View Controller 需要設定

StoryBoardID

因為 Main StoryBoard 已經包了一個 Navigation View Controller ,所以不能以 Navigation View Controller 當開頭

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];}

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];

}

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];

}

212

謝謝

top related