申し訳ありませんが、このシリーズは実現できません。ただ、興味のあるあなたに最も充実した詳細な 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 つの四角形の追加順序が簡単にわかります。赤が先で緑が後です。
⚠️:
- このような特性は、インタラクションが無効になる原因の一つである可能性があります。重なり合う 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:
- 新しいプロジェクトを作成すると、自動的に 1 つの 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)フレームと 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
データを設定します。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 番目の方法とは異なり、これは一般的に異なるビジネスインターフェース間の切り替えに使用され、段階的に戻ることができます。
- 表示(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 内のデータを選択し、適切な操作を行い、最終結果を生成します。