Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

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

5,666 views

Published on

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


  • DOWNLOAD FULL BOOKS, INTO AVAILABLE FORMAT ......................................................................................................................... ......................................................................................................................... 1.DOWNLOAD FULL. PDF EBOOK here { https://tinyurl.com/y8nn3gmc } ......................................................................................................................... 1.DOWNLOAD FULL. EPUB Ebook here { https://tinyurl.com/y8nn3gmc } ......................................................................................................................... 1.DOWNLOAD FULL. doc Ebook here { https://tinyurl.com/y8nn3gmc } ......................................................................................................................... 1.DOWNLOAD FULL. PDF EBOOK here { https://tinyurl.com/y8nn3gmc } ......................................................................................................................... 1.DOWNLOAD FULL. EPUB Ebook here { https://tinyurl.com/y8nn3gmc } ......................................................................................................................... 1.DOWNLOAD FULL. doc Ebook here { https://tinyurl.com/y8nn3gmc } ......................................................................................................................... ......................................................................................................................... ......................................................................................................................... .............. Browse by Genre Available eBooks ......................................................................................................................... Art, Biography, Business, Chick Lit, Children's, Christian, Classics, Comics, Contemporary, Cookbooks, Crime, Ebooks, Fantasy, Fiction, Graphic Novels, Historical Fiction, History, Horror, Humor And Comedy, Manga, Memoir, Music, Mystery, Non Fiction, Paranormal, Philosophy, Poetry, Psychology, Religion, Romance, Science, Science Fiction, Self Help, Suspense, Spirituality, Sports, Thriller, Travel, Young Adult,
       Reply 
    Are you sure you want to  Yes  No
    Your message goes here
  • Hello! Get Your Professional Job-Winning Resume Here - Check our website! https://vk.cc/818RFv
       Reply 
    Are you sure you want to  Yes  No
    Your message goes here

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

  1. 1. iOS App Development 1
  2. 2. 資料下載 https://dl.dropboxusercontent.com/u/14692540/iphoneui.zip 2
  3. 3. Who am I?  林銘賢  白天  網路公司工程師  晚上  南台講師  其它時間  導航 App Navier HUD iOS 版 作者
  4. 4. Navier HUD iOS
  5. 5. Navier HUD iOS
  6. 6. Navier HUD iOS
  7. 7. Navier HUD iOS
  8. 8. 課程大綱  iOS App 基礎  Tab Bar  Page Control  Table  Gesture  Collection  Timer and Thread  iAd and IAP 8
  9. 9. iOS App Basics 9
  10. 10. View 階層架構 10
  11. 11. View 階層架構 11
  12. 12. 畫面坐標原點在左上角 12
  13. 13. Frame 與 Bounds 13
  14. 14. App 沙盒 (SandBox) 每個 App 都有自 獨立的目錄 14
  15. 15. 常用目錄 15 目錄名稱 說明 <Application_Home>/<AppName>.app App bundle。不能在這個目錄寫 入任何資料 <Application_Home>/Documents 存放一般資料,不會被系統清除 <Application_Home>/Library 存放與 code 有關,但非使用者 資料的地方 <Application_Home>/tmp 暫存的目錄,會被系統清除
  16. 16. 轉向 直立 直立 上下顛倒 橫向-左 橫向-右 16
  17. 17. 啟動APP 17
  18. 18. 切換 App 執行進入 背景模式 18
  19. 19. 從背景模式切換到前景模式 19
  20. 20. 20
  21. 21. 視窗架構 21
  22. 22. 視窗架構 22 實際上是由 View Controller 負責提供 UIView 給 Windows
  23. 23. init loadView viewDidLoad viewWillAppear viewDidAppear viewWillDisappear viewDidDisappear viewDidUnload dealloc View Controller 的生命週期 23
  24. 24. View Controller 架構 24
  25. 25. View Controller 架構 25
  26. 26. 26
  27. 27. 27
  28. 28. 28
  29. 29. Navigation Push Pop Push Pop 29
  30. 30. 30
  31. 31. 31
  32. 32. Modal 32
  33. 33. Segue Segue Push 把目標 View Controller 加到 Navigation 堆疊最上層 Modal 顯示目標 View Controller Custom 由使用者自定訂義 View Controller 之間的轉換 33
  34. 34. Segue Push 與 Modal 的比較  Push  較有結構,有 Navigation 堆疊幫忙回上一層 View Controller  有 Navigation Bar  需要有 Navigation View Controller  Modal  可任意帶出下一個 View Controller  需自行定義 View Controller 之間的關係 34
  35. 35. 專案設定 35
  36. 36. 專案設定  Xcode 5.1  iPhone Retina 4 inch, 64 bit 36
  37. 37. 37
  38. 38. 設定 framework 加入 iAd.framework ( iAd 廣告) StoreKit.framework (In App Purchase, IAP, 內購) 38
  39. 39. 專案資料夾 39
  40. 40. 變更預設 StoryBorad 40
  41. 41. 設定 App Icon 41
  42. 42. 設定 App 圖示與起始畫面 42 圖示 起始畫面
  43. 43. 設定 App 圖示與起始畫面 43
  44. 44. 設定起始畫面 44
  45. 45. Tab Bar 45
  46. 46. Tab Bar 範例 46
  47. 47. Tab Bar 架構 47
  48. 48. Tab Bar 設計 48
  49. 49. 49
  50. 50. 50
  51. 51. 新增 Tab Bar 的 View Controller 51 滑屬右鍵
  52. 52. 新增 Tab Bar 的 View Controller 52
  53. 53. 53
  54. 54. Page Control 54
  55. 55. Page Control 55
  56. 56. 使用到的元件 56
  57. 57. 57
  58. 58. 58
  59. 59. 59
  60. 60. 新增界面元件與 View Controller 的關係 60 選擇 Automatic, 讓 Xcode 自動幫 你列出相關的 View Controller
  61. 61. 新增界面元件與 View Controller 的關係 61 建立 ScrollView Outlet 設定 ScrollView 的 delegate
  62. 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. 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. 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. 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
  66. 66. Table 66
  67. 67. 功能  Table 資料的顯示  顯示詳細內容  移動  刪除  Navigation Bar  跨 View Controller 之間的資料傳遞 67
  68. 68. 68
  69. 69. 69
  70. 70. 70
  71. 71. 嵌入 Navigation Controller 71
  72. 72. 72
  73. 73. Table View  UITableViewDataSource  提供要顯示的資料  UITableViewDelegate  處理 TableView 上的操作及事件 73
  74. 74. Table View  設定 dataSource 與 delegate 74
  75. 75. 75
  76. 76. 新增 Push Segue 76
  77. 77. 新增 Push Segue 77
  78. 78. 78
  79. 79. 設定自訂的 View Controller 類別  MyTableViewController  繼承 UITableViewController 79
  80. 80. MyTableViewController.m @interface MyTableViewController () { NSMutableArray *titles; NSString *selectedString; } @end 80
  81. 81. 準備 Table 資料 81
  82. 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. 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. 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
  85. 85. 處理資料編輯(刪除) 85
  86. 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
  87. 87. 處理資料移動 87
  88. 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. 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. 90. 處理資料選擇及 跨 View Controller 之間的資料 傳遞 90
  91. 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. 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. 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; }
  94. 94. Gesture 手勢處理 94
  95. 95. 手勢類型 95 Tap 點擊 Rotation 旋轉 Pinch 縮放
  96. 96. 手勢類型  Swipe (掃動)  固定方向  上下左右  Pan (拖拉)  任何方向  上下左右  斜對角 96
  97. 97. 處理手勢的類別 97
  98. 98. 元件 98
  99. 99. Tap 點擊不連續手勢類型 99
  100. 100. 狀態轉換 100
  101. 101. Pinch 縮放連續手勢類型 101
  102. 102. 102
  103. 103. UIGestureRecognizerState 103 UIGestureRecognizerStateBegan 開始 UIGestureRecognizerStateChanged 改變中,連續型的才有 UIGestureRecognizerStateEnded 結束 UIGestureRecognizerStateCancelled 取消 UIGestureRecognizerStateFailed 失敗 UIGestureRecognizerStateRecognized 與UIGestureRecognizerStateEnded 相同
  104. 104. 104
  105. 105. 取消 Auto Layout 105 取消 Auto Layout
  106. 106. Tap (點擊) 106  按一下跳至下一層 點擊次數 手指數
  107. 107. Tap (點擊)  透過 Segue 直接連到 Swipe View Controller 107
  108. 108. Swipe (掃動) 108 透過左右滑動回到上一層或下一層
  109. 109. 連接 Tap 與 Swipe 109 加入 Bar Button Item 並建立 Segue
  110. 110. 110 選擇 Push
  111. 111. 111 建立的 Segue
  112. 112. 112
  113. 113. Bar Item 113 設定名稱
  114. 114. Swipe (掃動) 114 右到左,透過 Segue 移動 左到右,透過Sent Action 處理
  115. 115. SwipeViewController.h // 處理 Swipe Gesture 由左滑到右邊事件 - (IBAction)handSwipeLeftToRight:(id)sender { // pop 回上一層 view controller [self.navigationController popViewControllerAnimated:TRUE]; } 115
  116. 116. Pan (拖拉) 116 透過左右滑動回到上一層或下一層
  117. 117. Pan (拖拉) 117 由 Sent Action 處理,藉由 x 方向的移動速度,判斷左右
  118. 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. 119. Long Press (長按)  長按切換按鈕顏色 119
  120. 120. Long Press (長按) 120 由 Sent Action 處理
  121. 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. 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. 123. Pinch (縮放)  利用 Pinch 縮放圖片大小 123
  124. 124. UIImageView 124
  125. 125. Pinch (縮放) 125 由 Sent Action 處理
  126. 126. PinchViewController.m @interface PinchViewController () { // 記錄上一次放大的倍數 CGFloat lastScale; } @end 126
  127. 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
  128. 128. Rotate (旋轉)  旋轉圖片 128
  129. 129. Rotate (旋轉) 129 由 Sent Action 處理
  130. 130. RotationViewController.m @interface RotationViewController () { // 記錄上次旋轉角度 CGFloat lastRotation; } @end 130
  131. 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. 132. 結合 Pan, Pinch, Rotation  可移動、縮放、旋轉圖片 132
  133. 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. 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
  135. 135. Collection 135
  136. 136. UICollectionViewController 136
  137. 137. Collection 設計 137
  138. 138. Collection Story Board 138
  139. 139. Collection View 139 勾選 Header 把 UIImageView 拉到 Cell
  140. 140. 140
  141. 141. 設定 Cell 及 Header 大小 141
  142. 142. 嵌入 Navigation View Controller 142 Embedded View Controller
  143. 143. 加入 Full Image View Controller 143
  144. 144.  建立 Section Header 的類別 及識別  建立 Cell 的類別 及識別  建立 Full Image View Controller 類別 144
  145. 145. Collection Reusable View  設定 Identifier 145
  146. 146. Collection Reusable View  設定 Identifier 146
  147. 147. Full Image View Controller UIImageView 設定 147
  148. 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. 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. 150. MyCollectionViewController.m #import "MySupplementaryView.h" #import "FullImageViewController.h" @interface MyCollectionViewController () { // 選擇的圖片 UIImage *selectedImage; // Secion 0 圖片名稱字串陣列 NSMutableArray *images0; // Secion 1 圖片名稱字串陣列 NSMutableArray *images1; } @end 150
  151. 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. 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. 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. 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. 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
  156. 156. Timer and Thread 156
  157. 157. Clock  使用 NSTimer 及 NSThread 實作時鐘  利用 UISwitch 切換 開關  使用 PickerView 選 擇時間格式 157
  158. 158. 158 設定 UISwitch 的 Value Changed 事件
  159. 159. 159 設定 Picker View 的 delegate 及 datasource
  160. 160. NSThread 限制  透過NSThread建立出來的執行緒與控制界面的主執行緒 是不一樣的  NSTimer 建立出來的計時器可以變更界面內容  NSThread 逼立出來的執行緒不能變更界面內容  需要回到主執行緒才能變更  performSelectorOnMainThread:withObject:waitUntilDone:  performSelectorOnMainThread:withObject:waitUntilDone:mo des: 160
  161. 161. 取得介面元件的另一個方法  介面元件都有一個 Tag  不一定要透過 Story Board 連接 View Controller 與介面 元件之間的關係  View Controller 可以透過 Tag 在程式執行期間,動態取 得介面元件 161 [self.view viewWithTag:1]
  162. 162. 設定 Tag 162
  163. 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. 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. 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. 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. 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. 168. ClockViewController.m // 執行緒本體 - (void)threadMain { while(isThreadAlive) { // 通知 Main Thread 執行 clockTimeout [self performSelectorOnMainThread:@selector(clockTimeout) withObject:nil waitUntilDone:FALSE]; // 睡 1 秒 sleep(1); } } 168
  169. 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
  170. 170. iAd 與 IAP 170
  171. 171. iAd 與 IAP  Simulator 不能測試 IAP  需要購買 iOS Developer Program  賺錢全靠他們倆  建立 IAP 項目  建立測試人員  不然你要一直花自己的錢去測試 IAP 嗎? 171
  172. 172. iOS Developer Program 172
  173. 173. 建立新的 App  iTune Connect  https://itunesconnect.apple.com 173
  174. 174. 174
  175. 175. 175
  176. 176. 上架時間及價格 176
  177. 177. Version Information 177
  178. 178. 178
  179. 179. App 資料 179
  180. 180. 聯絡資訊 180
  181. 181. 上傳 App 圖片 181
  182. 182. 182 新增 IAP
  183. 183. 新增 IAP 183
  184. 184. 選擇單賣的方式 184
  185. 185. IAP 項目名稱及價格 185
  186. 186. 設定 IAP 項目語言 186
  187. 187. 新增測試人員 187
  188. 188. 新增測試人員 188
  189. 189. 新增測試人員  Email 可以亂填沒關係,不會認證,只會當 ID 用 189
  190. 190. 問題  如何取得 IAP 項目資料?  如何購買 IAP 項目?  如何取得已購買項目?  如何記録已購買項目? 190
  191. 191. iAd 廣告
  192. 192. 192
  193. 193. 193 設定按鈕Touch Up Inside 事件處理
  194. 194. iAd 及 IAP framework  加入 iAd.framework 及 StoreKit.framework 194
  195. 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. 196 取得 IAP 項目清 單 讀取 User Default 更新 IAP 項目界面 啟動 App 處理 SKPaymentQueue 交易事件 購買 SKPaymentQueue addPayment 記錄購買項目在 User Default 處理 SKPaymentQueue 交易事件 恢復購買 SKPaymentQueue restoreCompletedTr ansactions 記錄購買項目在 User Default
  197. 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. 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. 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. 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. 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. 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. 203. 處理 SKPaymentQueue 交易事件 203 項目 說明 SKPaymentTransactionStatePurchasing 購買處理中 SKPaymentTransactionStatePurchased 購買成功 SKPaymentTransactionStateFailed 購買/恢復失敗 SKPaymentTransactionStateRestored 恢復成功 enum SKPaymentTransactionState
  204. 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
  205. 205. 整合所有範例 205
  206. 206. 統整 Story Board 206 Main Tab Bar Page Control Table Collection Clock iAd
  207. 207. 版面設計 207
  208. 208. 設定 StoryBoard ID  View Controller 需要設定 StoryBoardID  因為 Main StoryBoard 已經包了一 個 Navigation View Controller, 所以不能以 Navigation View Controller 當開頭 208
  209. 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. 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. 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
  212. 212. 謝謝 212

×