申し訳ありませんが、このシリーズは実現できません。ただ、興味のあるあなたに最も充実した詳細な iOS 学習体験を提供するために、全文を読むには 3 時間、30 分、3 分、または 3 秒かかるかもしれません。その後、どれだけの読者が 3 秒だけ持続したかを分析し、改善を続けます。
「虾票票」から iOS 入門シリーズ(3)—— よく使う UI コンポーネントに戻ってきてくれてありがとう。このエピソードでは、iOS のシステムアーキテクチャ、UIKit コンポーネントの重要なメンバー、およびそれらの基本的な使用法と特性、「虾票票」UI の解剖についてお話しします。
iOS システムアーキテクチャ#
iOS のシステムアーキテクチャは主に 4 つの層に分かれており、下から上に向かって、コアシステム層(Core OS)、コアサービス層(Core Services)、メディア層(Media)、およびタッチ層(Cocoa Touch)があります。その上にはアプリケーション(Application)が構成されています。
さらに細かい粒度でこの 4 つの層の主要な構成フレームワークを見てみましょう:
入門者にとっては、主にタッチ層の「UIKit フレームワーク」➕コアサービス層の「Foundation フレームワーク」に注目すれば良いです。以下の紹介は Apple の開発者ドキュメントからのものです:
- UIKit:iOS または tvOS アプリケーションのグラフィカルでイベント駆動型のユーザーインターフェースを構築および管理します;
- Foundation:基本データ型、コレクション、およびオペレーティングシステムサービスにアクセスして、アプリケーションの基本機能層を定義します。
それらのフレームワークの構成は附録 1 および附録 2 で確認できます~
Foundation については、まず基本データ型のカプセル化に注目してください。詳しくはObjective_C 基礎フレームワークをご覧ください —— 易百チュートリアル;UIKitについては、今日の主役です❗️
UIKit#
以下は UIKit の重要なメンバーの継承関係とそれぞれの役割です:
- UIView—— コンテンツを表示し、インタラクションを提供
- UIImageView—— 画像を表示
- UILabel—— テキストを表示
- UIScrollView—— 画面より大きなコンテンツを表示(地図など)
- UITableView—— 表形式のコンテンツを表示(電話帳など)
- UIViewController——View の管理コンテナ
- UITabBarController—— 複数の ViewController の切り替えを管理、タブ切り替え方式
- UINavigationController—— 複数の ViewController の切り替えを管理、プッシュ・ポップ切り替え方式
これらについて大まかな理解を得た後、小さな練習をしましょう。以下の 2 つのアニメーションには、主にどの UIView とどの UIViewController が含まれていますか:
もしあなたがそれらの基本的な構成をすぐに判断できるなら、UIKit の基本コンポーネントの役割について基本的な理解があることを示しています~
では、これらの基本コンポーネントを使用するにはどうすればよいのでしょうか、またそれぞれにどのような特性があるのでしょうか?
まずは、その中の 2 人の重要なメンバー:UIView と UIViewController についてお話ししましょう。
UIView#
コンテンツを表示し、インタラクションを提供
基本使用#
// 1)alloc:UIViewのためにメモリを確保;init:オブジェクトを初期化
UIView *view = [[UIView alloc] init];
// 2)左上隅の座標(100, 100)、幅と高さ100 * 100
view.frame = CGRectMake(100, 100, 100, 100);
// +背景色を設定
view.backgroundColor = [UIColor redColor]; // UIColor.redColorと同等
// 3)新しく定義したviewを親viewに追加
[self.view addSubview:view];
以下の 3 ステップは必須です。(❗️:UIView タイプのコンポーネントはほとんどこの 3 ステップを必要とします)
1)初期化:まず UIView のクラスメソッド alloc を呼び出してメモリを確保し、返されたインスタンスで init メソッドを呼び出してオブジェクトを初期化します;
2)フレームを設定:つまり、左上隅の座標と幅・高さ;
3)追加:定義した UIView オブジェクトを親 UIView オブジェクトに追加します。
特性#
1)スタック構造で子ビューを管理:複数の子ビューを追加でき、後に追加されたビューは先に追加されたビューの上に表示され、親ビューは子ビューのビュー階層を管理できます。
上の図から、2 つの四角形の追加順序が簡単にわかります。赤が先で緑が後です。
⚠️:
- この特性は、インタラクションが無効になる原因の 1 つである可能性があります。重なり合う View がある場合、上層のインタラクションが下層のインタラクションを無効にします。
- 注意深い方は、コード内のselfがどのような存在であるか疑問に思うかもしれません。その view プロパティは、次に述べる第 2 の重要なメンバーです。
OC はオブジェクト指向プログラミング言語であり、self は実際にはこのメソッドを呼び出しているオブジェクトを指します。上記の self のタイプは UIViewController です。
UIViewController#
View の管理コンテナ
MVC パターン(末尾の附録 3 を参照)を理解していれば、UIView との関係は非常に理解しやすいです:UIView は MVC の V--View であり、UIViewController は MVC の C--Controller です。
基本使用#
// 1)alloc:UIViewControllerのためにメモリを確保;init:オブジェクトを初期化
UIViewController *viewController = [[UIViewController alloc] init];
// 2)特定のviewをviewControllerのviewに追加
[viewController.view addSubview:someView];
1)初期化
2)管理する UIView オブジェクトを追加
特性#
1)コンテナとして機能し、デフォルトの view プロパティを持ち、そのタイプは UIView です。これは上文と呼応しています❗️
RootView は UIViewController オブジェクトに自動的に付属する UIView オブジェクトです。
2)以下はそのライフサイクルであり、開発者は適切なタイミングでオーバーライドしてカスタム操作を追加できます。
- init ->loadView->
- viewDidLoad->viewWillAppear->viewDidAppear->
- viewWillDisappear->viewDidDisappear->
- dealloc
- この図は quanqingyang の CSDN ブログから引用されており、UIViewController オブジェクトが初期化👉ビューを読み込み👉ビューを表示👉ビューが消える👉ビューをアンロード👉破棄されるプロセスをよく説明しています。
- 初心者は一般的に init と viewDidLoad に注目し、init 時にカスタムオブジェクトの初期化を追加し、viewDidLoad 時に self.view にカスタムの子ビューを追加します。
PS:
- 新しいプロジェクトを作成すると、自動的に ViewController(.h ヘッダーファイルと.m ソースファイル)が生成され、デフォルトでプロジェクト全体のルート ViewController として機能します。
- 一般的に、UIViewController を継承したカスタム ViewController をカプセル化し、その中に特定のビューとインタラクションロジックを追加します。
2 人の重要なメンバーについて話した後、少し休憩しましょう~次に、彼らの子孫の基本的な使用法と特徴を見てみましょう。
UIView の子孫たち#
UIImageView#
画像を表示
基本使用#
// 1)alloc:UIImageViewのためにメモリを確保;init:オブジェクトを初期化
UIImageView *imageView = [[UIImageView alloc] init];
// 2)左上隅の座標(100, 100)、幅と高さ100 * 100
imageView.frame = CGRectMake(100, 100, 100, 100);
// 3)画像のフィルモードを設定
imageView.contentMode = UIViewContentModeScaleAspectFill;
// 4)指定した画像をUIImageオブジェクトとして定義し、imageViewのimageプロパティに割り当てる
#define kImageName @"./ford.jpg"
imageView.image = [UIImage imageNamed:kImageName];
1)初期化
2)フレームを設定
3)画像のフィルモード contentMode を設定
主に以下のものがあります:
上記のコードで設定された contentMode は UIViewContentModeScaleAspectFill で、上の図の 2 番目の例に対応します。これは比較的一般的な方法です。
4)image プロパティに値を設定
すべての画像は最初に UIImage オブジェクトにカプセル化され、その後 UIImageView で表示されます。
特性#
1)動的画像を表示:UIImageView の animationImages プロパティに UIImage 型の配列を渡す;
2)関連するサードパーティライブラリ:SDWebImage、これは UIImage の生成プロセスを最適化し、一般的にネットワーク画像を取得するために使用されます。
PS:SDWebImage:https://github.com/SDWebImage/SDWebImage
UILabel#
テキストを表示
基本使用#
// 1)初期化
UILabel *summaryTabLabel = [[UILabel alloc] init];
// 2)左上隅の座標(100, 100)、幅と高さ100 * 100
imageView.frame = CGRectMake(100, 100, 100, 100);
// 3)テキストを設定
summaryTabLabel.font = [UIFont systemFontOfSize:14];
summaryTabLabel.textAlignment = NSTextAlignmentCenter;
summaryTabLabel.textColor = [UIColor orangeColor];
[summaryTabLabel setText:@“映画の概要"];
// PS:ジェスチャーを設定
UITapGestureRecognizer *tapGesture = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(summaryTabTapAction)];
[summaryTabLabel addGestureRecognizer:tapGesture];
summaryTabLabel.userInteractionEnabled = YES;
1)初期化
2)フレームを設定
3)テキストを設定:フォント、整列方法、テキストの色、テキストの内容
PS)ジェスチャーを設定
「虾票票」詳細ページのタブバー(映画の概要、キャスト情報、その他の情報)は UILabel を基に実装されており、クリックのジェスチャーを追加することでボタンのようなクリック効果を実現できます。
⚠️:UILabel オブジェクトのインタラクションはデフォルトで無効になっているため、userInteractionEnabled プロパティを YES に設定して有効にする必要があります。
特性#
1)複数行表示(上のアニメーションの各タブバー内の内容のように)
summaryTabLabel.numberOfLines = 0; // デフォルトは1
[summaryTabLabel sizeToFit];
2)テキストの切り捨て方法:lineBreakMode プロパティ、文字で改行する、間の部分を省略して表示するなど
3)複雑なテキストを表示:NSAttributeString 型を使用、YYText(サードパーティライブラリ)
UIScrollView#
画面より大きなコンテンツを表示
基本使用#
// 1)初期化し、可視範囲を設定——frame
UIScrollView *detailScrollView = [[UIScrollView alloc] initWithFrame:self.view.frame];
// 2)スクロール範囲を設定——contentSize
#define kScreenWidth UIScreen.mainScreen.bounds.size.width
#define kScreenHeight UIScreen.mainScreen.bounds.size.height
detailScrollView.contentSize = CGSizeMake(kScreenWidth * 3, kScreenHeight);
// PS:ページ単位でスクロールするかどうか
detailScrollView.pagingEnabled = YES;
1)初期化し、フレームを設定
フレームはこのビューの可視範囲であり、初期化時に設定できます。initWithFrame を使用する必要があります。
上記のコードで設定された self.view.frame は self.view のフレームです。self.view が何であるかはご存知でしょう。
2)contentSize を設定
contentSize はこのビューのスクロール範囲であり、言い換えれば、その全範囲です。
PS)ページ単位でスクロールするかどうかを設定:pagingEnabled プロパティ
「虾票票」のこのアニメーションを見てみると、contentSize は画面幅の 3 倍で、ページ単位でスクロールします。
特性#
1)frame と contentSize
前者は可視範囲、後者はスクロール範囲です。
これら 2 つは UIScrollView 初期化時の最も重要な 2 つのプロパティであり、設定が不適切だとスクロールしない可能性があります~
2)setContentOffset メソッド
[detailScrollView setContentOffset:CGPointMake(detailScrollView.bounds.size.width * 2, 0) animated:YES];
前の小節で、タブバーをクリックして UIScrollView をスクロールさせる際、UILabel のクリックジェスチャーに対応するイベント summaryTabTapAction 内で setContentOffset メソッドを使用する必要があります。
以下は UIView の子孫の中で最も若いもので、UIScrollView を継承しており、ここで最も理解が難しいものです。
UITableView : UIScrollView#
表形式を表示
基本使用#
// 1)初期化し、可視範囲を設定——frame
UITableView *homeTableView = [[UITableView alloc] initWithFrame:self.view.bounds];
// 2)dataSource、delegate代理を指定(クラス宣言時に2つのプロトコルを追加することを忘れずに)
homeTableView.dataSource = self;
homeTableView.delegate = self;
// 3)次にプロトコル内のメソッドを実装する(@requiredは必須項目、@optionalはオプション項目)
1)初期化し、フレームを設定
フレームは可視範囲で、UIScrollView と同様です。
2)dataSource と delegate の代理を指定
dataSource と delegate は UITableView の 2 つのプロトコルであり、「代理を指定する」というのは「他者」にこのプロトコルを遵守させ、プロトコル内のいくつかのことを手伝ってもらうという意味です。これは私たちの生活の中の労働契約や賃貸契約に例えることができます。
i. dataSource は表のデータソース、セルの内容などを担当します;
ii. delegate はセルのインタラクション、設定などを担当します。
ここで明確にする必要があるのは、上記のコードはController内に書かれており、self は UIViewController タイプのオブジェクトを指します。つまり、self はこれら 2 つのプロトコルを遵守し、プロトコル内のメソッドを実装する必要があります。
3)プロトコル内のメソッドを実装
i. dataSource
#pragma mark - UITableViewDataSource
// @required
// 1)各セクションのセル数を設定
(NSInteger)tableView:(UITableView *)tableView
numberOfRowsInSection:(NSInteger)section {
return 3;
}
// 2)各セルの内容をレンダリング
(UITableViewCell *)tableView:(UITableView *)tableView
cellForRowAtIndexPath:(NSIndexPath *)indexPath {
// 1.再利用プールからkCellIdに対応するセルを探す(#define kCellId@“id1”)
// [tableView dequeueReusableCellWithIdentifier:kCellId];
// 1-2.存在しない場合は、手動で相応のセルを生成し、kCellIdとして登録
// [… reuseIdentifier:kCellId];
// 2.セルデータを設定
// 3.return cell
}
// @optional…
dataSource プロトコルには 2 つの @required
(必須実装)といくつかの @optional
(オプション実装)メソッドがあります。
最初の @required
メソッドは、各セクション(分区)のバッファセル数を設定するためのもので、 return 3
は 3 つのデータをレンダリングすることを示します。実際の使用では、一般的にネットワークリクエストから返されたデータの数に基づいて返す数を決定します。
2 番目の @required
メソッドは、各セルのレンダリング内容を設定するためのもので、コードのコメントを参考に、一般的に 3 つのステップに分かれます:
- 再利用プールから
kCellId
(カスタムセルの識別子)に対応するcell
を探します。存在しない場合は、手動で相応のcell
を生成し、再利用プールにその識別子を登録します。 cell
データを設定します。return cell
します。
PS:再利用プールについては後で再度言及します。UITableViewCell タイプは UIView を継承しており、その基本的な使用法と特性はほぼ同じです。
ii. delegate
#pragma mark - UITableViewDelegate
// @optional
// 1)各セルの行の高さを設定
(CGFloat)tableView:(UITableView *)tableView
heightForRowAtIndexPath:(NSIndexPath *)indexPath {
return 100;
// 2)セルが選択された後のイベントを設定
(void)tableView:(UITableView *)tableView
didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
// セル選択後の処理
}
// …
delegate プロトコルにはいくつかの @optional
(オプション実装)メソッドがあり、上記には 2 つの一般的な待機実装メソッドが示されています。
最初の @optional
メソッドは、各セルの行の高さを設定するためのものです。
2 番目の @optional
メソッドは、セルが選択された後のイベントを設定するためのもので、例えば別のページにプッシュすることができます。
「虾票票」のホームページを参考にして、上記の 4 つの待機実装メソッドの役割を感じてみてください:
この映画表の映画の数はネットワークリクエストのデータに基づいて決定されます。各セルのフォーマットは再利用可能です。行の高さは固定されています。特定の映画をクリックした後の画面は自分で想像してください。
特性#
1)UITableView は表示のみを担当し、データ、セル、およびインタラクションは開発者が提供する必要があります。
i. dataSource:データソース、セル内容など
ii. delegate:セルインタラクション、設定など
2)再利用プール
セルの生成と破棄はパフォーマンスを消費するため、再利用プールメカニズムが存在します。その基本原理は以下の通りです:
再利用プールの基本的な実装は双方向キュー(dequeue)に基づいており、画面を上にスクロールすると、上のセルが消え、システムによって自動的に回収され、下のセルが読み込まれる際には、その唯一の識別子 id を使って再利用プールを検索します。見つかった場合は成功裏に再利用され、見つからなかった場合は手動で相応のセルを生成し、再利用プールに登録します。
以下の図から再利用プールの本質を理解できます:
上にスクロールする際、新しく読み込まれたセルはセルのスタイルだけでなく、右側の緑のチェックマーク✅も再利用されます。
実際の使用では、これを行うのは少し滑稽ですので、一般的には再利用セルの後にそのデータを設定します。上記の基本使用の第 3 ステップで言及しました。または、prepareForReuse
メソッドをオーバーライドして、セルを再利用する前に不要なデータをクリアします。(注:prepareForReuse
は UITableViewCell のメソッドです)
ここで、UITableView の完全な構成を理解しましょう:
先ほど展示した「虾票票」Demo は単一のセクションであり、ヘッダーやフッターは設定されていません。
実際には、完全な UITableView は上の図を参考にし、次のように公式で表現できます:
UITableView = tableHeaderView + n ✖️ Section + tableFooterView
ここで、Section = sectionHeader + n ✖️ UITableViewCell + sectionFooter
ついに、ここまで頑張ってきたあなたはいますか?「UIView の子孫たち」の打刻ポイント、💳ドロップ~
以下は UIViewController の 2 人の優れた子供たちです。
UIViewControllerの子供たち#
この 2 人の子供の共通点は:複数の UIViewController オブジェクトの切り替えを管理できることです。聞こえは、息子も複数の父親を管理できるようです~😛
異なる点は以下の紹介で対照的に体験できます:
UITabBarController#
基本使用#
// 1)初期化
UITabBarController *tabbarController = [[UITabBarController alloc] init];
// 2)管理する複数のControllerを設定(UIViewControllerを属するか継承する)
[tabbarController setViewControllers:@[controller1, controller2, controller3]];
1)初期化
2)管理する複数の Controller を設定
最初に設定されており、各 Controller のタイプは UIViewController に属するか継承します。
デモを見てみましょう:
この UITabBarController オブジェクトは 3 つの Controller を管理しています。
特性#
1)UITabBar、すなわちページ下部の切り替えバー:
管理されている UIViewController オブジェクトは自分で UITabBar上の対応するタブの内容を変更します:
controller1.tabBarItem.title = @“ニュース";
controller1.tabBarItem.image = [UIImage imageNamed:@"tab1.png"];
アイコンやタイトルなど。
UINavigationController#
基本使用#
// 1)初期化し、ルートControllerを設定
UINavigationController *navigationController = [[UINavigationController alloc] initWithRootViewController:controller1];
// 2)適切なタイミングで別のControllerをプッシュ
[navigationController pushViewController:controller4 animated:YES];
1)初期化し、ルート Controller を設定
1 つだけ設定すれば良いです。
2)その後、適切なタイミングで別の Controller をプッシュします。
「虾票票」では、ホームページの特定の映画をクリックすると、対応する詳細画面の Controller にプッシュされます。
デモを見てみましょう:
Show ボタンをクリックすると、別の Controller がプッシュされました。
特性#
1)UINavigationBar、すなわちページ上部のナビゲーションバー:
同様に、UIViewController オブジェクトは自分で UINavigationBarの内容を変更します:
controller4.navigationItem.title = @“内容";
タイトルなどを設定でき、両側のボタンをカスタマイズすることもできます。
2)スタック管理:上のレベルに戻ることも、ルートビューまたは指定されたビューに戻ることもできます。
上記の説明を通じて、UITabBarController と UINavigationController の違いを理解しましたか?
以下に 2 つの知識点を延長します。良い学生が最も好きなセクション~実際に上記の内容を深く理解するのにも役立ちます。
延長 1:一般的なページ切り替え方法#
先ほどの 2 つの Controller から、2 つの異なるページ切り替え方法を感じ取ることができますが、一般的なページ切り替え方法にはどのようなものがありますか?
- タブ:誰を指しているか知っていますよね~
- プッシュ / ポップ:誰を指しているか知っていますよね~
- プレゼント / 解散:2 番目の方法とは異なり、一般的に異なるビジネスインターフェース間の切り替えに使用され、逐次的に戻ることができます。
- show(iOS 8.0+)
この方法は、ViewController のタイプに応じて自動的に切り替え方法を選択します。例えば、splitViewController の切り替え方法は以下の通りです:
一般的に iPad デバイスで見られます。例えば、淘宝アプリなど。
延長 2:2 つのネスト方法#
実際の使用では、UITabBarController と UINavigationController はしばしば組み合わせて使用されます。組み合わせ方というよりも、ネスト方法と言った方が良いでしょう。
方法一:
方法二:
これら 2 つの方法の最も明確な違いは、ページ切り替え時にTabBar が消えるかどうかです。
しかし、なぜ Apple 公式が最初の方法を推奨するのでしょうか?要約すると、2 つの理由があります:
1)自由度が高い:各 NavigationController は独立しており、NavigationController をネストしないことも選択できます;
2)方法二と同じページ切り替え効果を実現でき、手動で Tab バーを隠すことができます。
注:UI 描画はすべてメインスレッドで行われる#
今日は UIKit の重要なコンポーネントについて多くのページを費やしましたが、それらを使用する際には最後のこのポイントを忘れないでください:UI 描画はすべてメインスレッドで行われます❗️
なぜでしょうか?ここではUI をメインスレッドで操作する必要がある理由—— 掘金のこの記事からの内容を引用します。
1)UIKit はスレッドセーフでないクラスです。UI 操作はさまざまな View オブジェクトの属性にアクセスすることを含み、非同期操作があると読み書きの問題が発生します。ロックをかけると大量のリソースを消費し、実行速度が遅くなります;
2)メインスレッドでのみイベントに応答できます。アプリケーションの起点である UIApplication はメインスレッドで初期化され、すべてのユーザーイベントはメインスレッドで伝達されます(クリック、ドラッグなど)、そのため UI はメインスレッドでのみイベントに応答できます;
3)画像の同期更新を確保します。レンダリングに関しては、画像のレンダリングは 60 フレームの更新率で画面上で同時に更新される必要がありますが、非メインスレッドの非同期化では、この処理プロセスが同期更新を実現できるかどうかは不明です。
したがって、UI 描画を行う際には、現在のスレッドがメインスレッドであるかどうかに注意してください!
「虾票票」UI 解剖#
では、熱が冷めないうちに、「虾票票」がどのような UIKit コンポーネントで構成されているかを見てみましょう。直接画像を見てみましょう!
👏今日は多くの収穫がありましたか?もしあなたが上の図を再現できるなら、UIKit をしっかりとマスターしていることは間違いありません!ぜひコメントであなたの意見を教えてください、またはどこに疑問があるか教えてください。
来週またお会いしましょう#
次回は Xcode でのデバッグ技術についてお話ししますので、お楽しみに。
附録#
1)Foundation
2)UIKit
コンポーネントの親クラスを観察することで、コンポーネントの機能を簡単に特定できます。
3)MVC パターン
MVC パターンはソフトウェアアーキテクチャパターンの一つで、MVC は 3 つの単語の頭文字を取ったもので、それぞれ Model(モデル)、View(ビュー)、Controller(コントローラー)を指します:
1)Model は、プログラムが操作するデータや情報です。
2)View は、ユーザーに提供される操作インターフェースであり、プログラムの外殻です。
3)Controller は、上記の 2 者の間に位置し、ユーザーが View に入力した指令に基づいて Model からデータを選択し、適切な操作を行い、最終結果を生成します。