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

212
iOS App Development 1

Upload: -

Post on 10-May-2015

2.319 views

Category:

Software


0 download

DESCRIPTION

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

TRANSCRIPT

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

iOS App Development

1

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

2

資料下載

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

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

Who am I?林銘賢

白天網路公司工程師

晚上南台講師

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

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

Navier HUD iOS

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

Navier HUD iOS

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

Navier HUD iOS

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

Navier HUD iOS

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

8

課程大綱 iOS App 基礎

Tab Bar

Page Control

Table

Gesture

Collection

Timer and Thread

iAd and IAP

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

9

iOS App Basics

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

10

View 階層架構

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

11

View 階層架構

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

12

畫面坐標原點在左上角

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

13

Frame 與 Bounds

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

14

App 沙盒 (SandBox)

每個 App 都有自獨立的目錄

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

15

常用目錄

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

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

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

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

16

轉向直立

直立上下顛倒

橫向 - 左 橫向 - 右

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

17

啟動 APP

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

18

切換 App 執行進入背景模式

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

19

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

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

20

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

21

視窗架構

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

22

視窗架構

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

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

23

init

loadView

viewDidLoad

viewWillAppear

viewDidAppear

viewWillDisappear

viewDidDisappear

viewDidUnload

dealloc

View Controller 的生命週期

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

24

View Controller 架構

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

25

View Controller 架構

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

26

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

27

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

28

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

29

Navigation

Push

Pop

Push

Pop

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

30

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

31

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

32

Modal

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

33

Segue

Segue

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

Modal 顯示目標 View Controller

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

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

34

Segue Push 與 Modal 的比較

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

Controller有 Navigation Bar需要有 Navigation View Controller

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

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

35

專案設定

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

36

專案設定Xcode 5.1

iPhone Retina 4 inch, 64 bit

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

37

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

38

設定 framework加入

iAd.framework ( iAd 廣告 )

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

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

39

專案資料夾

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

40

變更預設 StoryBorad

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

41

設定 App Icon

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

42

設定 App 圖示與起始畫面

圖示

起始畫面

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

43

設定 App 圖示與起始畫面

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

44

設定起始畫面

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

45

Tab Bar

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

46

Tab Bar 範例

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

47

Tab Bar 架構

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

48

Tab Bar 設計

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

49

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

50

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

51

新增 Tab Bar 的 View Controller

滑屬右鍵

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

52

新增 Tab Bar 的 View Controller

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

53

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

54

Page Control

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

55

Page Control

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

56

使用到的元件

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

57

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

58

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

59

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

60

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

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

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

61

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

建立 ScrollView Outlet

設定 ScrollView 的 delegate

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

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

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

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

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

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;

}

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

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

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

66

Table

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

67

功能Table 資料的顯示

顯示詳細內容

移動

刪除

Navigation Bar

跨 View Controller 之間的資料傳遞

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

68

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

69

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

70

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

71

嵌入 Navigation Controller

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

72

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

73

Table ViewUITableViewDataSource

提供要顯示的資料

UITableViewDelegate處理 TableView 上的操作及事件

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

74

Table View設定 dataSource 與 delegate

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

75

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

76

新增 Push Segue

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

77

新增 Push Segue

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

78

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

79

設定自訂的 View Controller 類別

MyTableViewController繼承 UITableViewController

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

80

MyTableViewController.m

@interface MyTableViewController ()

{

NSMutableArray *titles;

NSString *selectedString;

}

@end

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

81

準備 Table 資料

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

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;

}

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

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

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

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

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

85

處理資料編輯 ( 刪除 )

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

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

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

87

處理資料移動

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

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

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

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

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

90

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

資料傳遞

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

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

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

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

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

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;

}

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

94

Gesture手勢處理

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

95

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

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

96

手勢類型Swipe (掃動 )

固定方向上下左右

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

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

97

處理手勢的類別

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

98

元件

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

99

Tap 點擊不連續手勢類型

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

100

狀態轉換

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

101

Pinch 縮放連續手勢類型

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

102

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

103

UIGestureRecognizerState

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

與 UIGestureRecognizerStateEnded 相同

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

104

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

105

取消 Auto Layout取消 Auto Layout

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

106

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

點擊次數 手指數

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

107

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

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

108

Swipe (掃動 )

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

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

109

連接 Tap 與 Swipe

加入 Bar Button Item 並建立 Segue

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

110

選擇 Push

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

111

建立的 Segue

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

112

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

113

Bar Item

設定名稱

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

114

Swipe (掃動 )

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

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

115

SwipeViewController.h

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

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

}

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

116

Pan (拖拉 )

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

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

117

Pan (拖拉 )

由 Sent Action 處理,藉由 x 方向的移動速度,判斷左右

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

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

}

}

}

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

119

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

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

120

Long Press (長按 )

由 Sent Action 處理

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

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;

}

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

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

}

}

}

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

123

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

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

124

UIImageView

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

125

Pinch (縮放 )

由 Sent Action 處理

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

126

PinchViewController.m

@interface PinchViewController ()

{

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

}

@end

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

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

}

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

128

Rotate (旋轉 ) 旋轉圖片

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

129

Rotate (旋轉 )

由 Sent Action 處理

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

130

RotationViewController.m

@interface RotationViewController ()

{

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

}

@end

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

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

}

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

132

結合 Pan, Pinch, Rotation

可移動、縮放、旋轉圖片

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

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

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

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;

}

}

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

135

Collection

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

136

UICollectionViewController

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

137

Collection 設計

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

138

Collection Story Board

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

139

Collection View

勾選 Header把 UIImageView拉到 Cell

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

140

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

141

設定 Cell 及 Header 大小

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

142

嵌入 Navigation View Controller

Embedded View Controller

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

143

加入 Full Image View Controller

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

144

建立 Section Header 的類別及識別

建立 Cell 的類別及識別

建立 Full Image View Controller 類別

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

145

Collection Reusable View

設定 Identifier

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

146

Collection Reusable View

設定 Identifier

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

147

Full Image View Controller UIImageView 設定

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

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

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

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;

}

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

150

MyCollectionViewController.m

#import "MySupplementaryView.h"

#import "FullImageViewController.h"

@interface MyCollectionViewController ()

{

// 選擇的圖片 UIImage *selectedImage;

// Secion 0 圖片名稱字串陣列 NSMutableArray *images0;

// Secion 1 圖片名稱字串陣列 NSMutableArray *images1;

}

@end

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

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

}

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

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

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

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

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

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

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

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

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

156

Timer and Thread

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

157

Clock

使用 NSTimer 及 NSThread 實作時鐘

利用 UISwitch 切換開關

使用 PickerView 選擇時間格式

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

158

設定 UISwitch 的 Value Changed 事件

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

159

設定 Picker View 的 delegate 及 datasource

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

160

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

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

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

performSelectorOnMainThread:withObject:waitUntilDone:

performSelectorOnMainThread:withObject:waitUntilDone:modes:

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

161

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

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

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

[self.view viewWithTag:1]

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

162

設定 Tag

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

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

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

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

}

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

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

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

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

}

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

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;

}

}

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

168

ClockViewController.m

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

{

while(isThreadAlive)

{

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

// 睡 1 秒 sleep(1);

}

}

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

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

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

170

iAd 與 IAP

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

171

iAd 與 IAPSimulator 不能測試 IAP

需要購買 iOS Developer Program

賺錢全靠他們倆

建立 IAP 項目

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

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

172

iOS Developer Program

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

173

建立新的 App

iTune Connecthttps://itunesconnect.apple.com

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

174

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

175

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

176

上架時間及價格

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

177

Version Information

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

178

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

179

App 資料

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

180

聯絡資訊

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

181

上傳 App 圖片

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

182

新增 IAP

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

183

新增 IAP

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

184

選擇單賣的方式

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

185

IAP 項目名稱及價格

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

186

設定 IAP 項目語言

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

187

新增測試人員

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

188

新增測試人員

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

189

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

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

190

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

如何購買 IAP 項目?

如何取得已購買項目?

如何記録已購買項目?

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

iAd 廣告

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

192

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

193

設定按鈕 Touch Up Inside 事件處理

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

194

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

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

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]

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

196

取得 IAP 項目清單

讀取 User Default更新 IAP 項目界面

啟動 App

處理 SKPaymentQueue

交易事件

購買

SKPaymentQueue addPayment

記錄購買項目在 User Default

處理 SKPaymentQueue

交易事件

恢復購買

SKPaymentQueue restoreCompleted

Transactions

記錄購買項目在 User Default

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

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

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

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

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

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

}

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

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

}

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

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;

}

}

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

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;

}

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

203

處理 SKPaymentQueue 交易事件

項目 說明SKPaymentTransactionStatePurchasing 購買處理中SKPaymentTransactionStatePurchased 購買成功SKPaymentTransactionStateFailed 購買 /恢復失敗SKPaymentTransactionStateRestored 恢復成功

enum SKPaymentTransactionState

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

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;

}

};

}

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

205

整合所有範例

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

206

統整 Story Board

Main

Tab BarPage

ControlTable

Collection

Clock iAd

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

207

版面設計

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

208

設定 StoryBoard ID View Controller 需要設定

StoryBoardID

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

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

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

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

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

}

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

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

}

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

212

謝謝