2020年6月5日金曜日

iPhoneアプリのiPad対応5 - Page Sheetの位置

iPad対応 第5回、Page Sheetの位置 について。

Page Sheetの位置

iPadのように大きな画面の場合、Page Sheetには下にマージンが付く。


上図の赤枠が、Page SheetのModal画面。
iPhoneや、iPadのSplit Viewで別アプリを同時表示などの場合、下のマージンはなく、上のマージンのみある。
そのため、iPhone Onlyアプリでは下マージンの考慮ができていなかった。

それが、Keyboard表示時に、Keyboardのサイズに合わせて画面を変える場合。


上図は、Keyboardの高さ分、ボタン類を上に移動させたいパターン。
Keyboardの高さから、画面下マージンの高さを引いた分だけ移動したい。
他にも、Keyboardの高さに合わせて、画面を上にスクロールさせる等 も同じ考慮が必要と思う。

だが、Modalの view.frame.origin は 0, 0 になっていて、view.frameでは上下マージンのサイズがわからない。

マージンの取得方法

結論から言うと、UIPresentationController から取得できる。
この例だと、self.presentationController?.presentedView?.frame の値は、(156.0, 40.0, 712.0, 688.0) になっていた。

UIPresentationControllerとは、画面の表示を管理するクラス。
今回取り上げたような、画面サイズによって表示方法を変えたり、画面表示時のアニメーションを制御したりする。
カスタムしたことがある人には、おなじみ。

iPad対応 おわり

これにて iPad対応おわり!
Recipe Note ver 1.3.0 として、先程アップデート完了。

iPad対応の他に、iOS 13 で追加された、検索のUISearchTokenや、LinkのリッチViewのLPLinkView も入れて楽しんでみたので、よければお試しください ー。



• • •

2020年6月2日火曜日

iPhoneアプリのiPad対応4 - TableCell制御

iPad対応 第四弾。テーブルのセルの表示制御について。

まず、目標を設定アプリで確認。

1. Accessory Type
縦向き (Small Size) のときは DisclosureIndicator
横向き (Regular Size) のときは None

2. 選択セル
横向き (Regular Size) のときは、セルの選択状態を保持

3. Table Style
縦向き (Small Size) のときは Detailテーブルは Grouped
横向き (Regular Size) のときは Detailテーブルは Inset Grouped

3については方法はわからなかった。Styleは作成時にのみ設定可能なので、途中で変えることはできなさそうだし...方法はあるのかな?
ということで、1 と 2 を実装していく。

Accessory Type

Size Classが変わったときに、Accessory Type を切り替えるようにする。
以下のようなクラスをMasterのTableCellに設定すると、実現できる。

Size Classは traitCollection.horizontalSizeClassで取得、変更タイミングは traitCollectionDidChange でトリガーできる。

選択セル

選択の解除

まずは、Detail が非表示になったときMasterの選択を外す処理を考える。
外す処理は、Masterの viewWillAppear に入れていくが、方法は UITableViewController のときと、UIViewController のときとで変える必要がありそう。

UITableViewControllerのときは、clearsSelectionOnViewWillAppear を設定すればOKのようだ。
UITableViewControllerのviewWillAppearの中には、選択を解除する処理が入っているみたいなので、その前に設定が必要。
UIViewControllerのときは、UITableView.deselectRow を組み込む。
DetailViewを「何も選択していないView」を使わず、「常になにか選択されている」ようにする場合は、1カラムから2カラムに変更されたときにも選択する処理が必要。

選択しないCell


Recipeアプリの設定画面では、上図の構成になっている。
これら選択状態にしないCellについての実装方法について。

タップ不可のCell

Switchがあるタップ不可のCellの実装は、Selection を Noneにするとともに、tableView(_:shouldHighlightRowAt:) で false を返す。
見た目では、SelectionをNoneにするだけでタップ不可に見えるけど、内部では選択する処理が入っているから、shouldHighlightRowAt の処理も必要。

タップ可で選択不可のCell

ヘルプなどのCellは、TapできるけどSafariで表示するだけなので選択状態にはしないCell。
この場合は、tableView(_:shouldHighlightRowAt:) は true で返し、tableView(_:willSelectRowAt:) の中で処理を入れ、戻り値のIndexPathをnil にする。


今回のTableCellの制御は、見かけの調整なので、実装しなくても実害はないけど、ちゃんと整えると気持ちが良い。
iPadだからという話題ではなかったけど、いままでiPhoneの縦のみのアプリが多かったのもあり、あまり気にしていなかったので、良いお勉強になった。


次回につづく。

• • •

2020年5月31日日曜日

iPhoneアプリのiPad対応3 - Drag and Drop

iPad対応 第3回目は、Drag and Drop。
SplitViewで、複数のアプリを同時に表示するメリットの一つが、他アプリのデータをDrag and Dropでコピーできること。

UITextField, UITextAreaは、デフォルトでテキストのDnDに対応している様子。
今回対応中のレシピアプリでは、画像が登録できる機能があるので、他アプリから画像をDropで追加できるようにしてみた。



Web上の画像をアプリに登録する場合は、かなり楽になる!
Drag and Dropの仕組みは、iOS 11以上のUITableView, UICollectionView で実装が可能。

Dropの実装

使用するのは、UITableViewDropDelegate

canHandleのメソッドで、受け取り可能なClassかをチェックする。今回はUIImageのみ可能に。

Dropの処理は、以下のように実装した。
UITableViewDropPlaceholder で一時的なViewを作り、処理が完了したら削除する。

Dragの実装

Dragを実装すると、外部アプリへデータをドラッグできるようになる。使用するのはUITableViewDragDelegate
itemsForBeginning で、受け渡したい情報を設定する。

DragDelegateとともに、tableView(_:canMoveRowAt:)tableView(_:moveRowAt:to:) などを実装すると、テーブル内のセルの順番をDnDで操作することも可能になる。
以下は、同じSection内に限りDnDで行を入れ替えることができる実装例。


外部アプリにデータを渡さないのであれば、itemsForBeginningの戻り値は設定なしでOK。

ただし、tableView(_:moveRowAt:to:) の処理中で、tableView.reloadSections で更新すると、次回から行移動ができなくなるバグ?があるみたいなので注意。tableView.reloadData() だと大丈夫のようだ。

iPadだと、登録がかなり楽になる! 素敵!
次回につづく。
• • •

2020年5月30日土曜日

iPhoneアプリのiPad対応2 - Readable Width

読みやすい表示幅 - Readable Width

iPhoneアプリのiPad対応。前回のSplitViewに続いて、今回は表示幅について。


画面の横幅が大きくなったとき、デフォルトのMarginサイズだと横に間延びして読みにくくなってしまう。
読みやすい表示幅を制御する仕組みはすでにOSにあり、Readable Width を設定すれば反映できる。
(iOS 12以上で利用可)

設定方法

Storyboard > Size Inspector の "Follow Readable Width" がその設定になる。

ちなみに、Preserve Superview Marginsは、SuperviewのMarginも考慮するプロパティ。カスタムテーブルセルを作るとき、標準セルのMarginと合わせることができます。

Readable Widthの幅は、OSで設定されているText Sizeによって変わるのに注意。
Text Sizeが大きくなると、幅は大きくなる。

デフォルトのMarginを無しでReadable Widthのみ反映する

レシピの画像は、Compactサイズでは左右Margin無し、Readable Widthが反応するサイズではそのMarginに反応するよう設定した。
この設定は以下の通り。

Layout Margins は 0 を設定し、Follow Readable Width にチェックをいれる。
これで、以下のように、画面幅に応じてMarginが切り替わる。


Readable Marginのサイズを取得する

UIViewのlayoutMarginsに、Readable Widthも含めたMarginが含まれている模様。
ただ、アプリ操作途中でOSのText Sizeを変えることによりReadable Widthの幅が変わった場合、layoutMargins には変更後の値は含まれていないっぽい。(iPadOS 13.5 で確認)


iPhoneアプリをiPad対応する際、Readable Marginの対応することは多くありそう。
Storyboardの設定だけで完結できるのはとても良い。

次回につづく。
• • •

iPhoneアプリのiPad対応1 - SplitView

iPadOSMac Catalyst のお勉強として、既存のiPhoneアプリ Recipe Note のiPad対応を試し中。WWDC 2020 でなにか進展がありそう予測も兼ねた復習として。
学んだこと、迷ったことを書いてみる。

OSになるべく逆らわず、以下の基本方針で。

  • iPhone/iPad で同じUI(Storyboard)を使う
  • UIDevice.currentDevice().userInterfaceIdiom でOSの条件分岐しない
  • Size Classでの条件分岐しない

UISplitViewController

iPadOSでSplit Viewの操作をすると、大きいサイズ(Regular) ⇔ 小さいサイズ(Small) が頻繁に切り替わることになる。
その操作をしても、いい感じの表示にするには、やはりUISplitViewController を使いたい。



UISplitViewController を使うポイント

状態の設定と取得

SplitViewの種類は、preferredDisplayMode で設定できる。
Master/Detailを両方表示(例: 設定アプリ)や、MasterをOverlay表示など。
isCollapsed で折りたたまれているかどうか?を取得できる。

displayModeButtonItem を、navigationItem.leftBarButtonItem に設定することにより、Back / 全画面表示 の処理をするボタンが表示できる。上記動画の右ペインの左上にあるボタンがそれ。状態によって表示も自動的に切り替わってくれる。

サイズに関する挙動差

Master(左ペイン) → Detail(右ペイン)と画面遷移していく、それらの画面がどこにStackされるのかは、Splitの表示状態によって異なる。
サイズがSmall、つまり1ペイン表示のときは、MasterのUINavigationViewControllerにStackされ、サイズがRegularのときは、UISplitViewControllerにStackされる。

iPhoneのときは初期表示をMasterのみにする

UISplitViewControllerのDetailにViewがあると、デフォルトでは初期表示はDetailになる。
iPhoneのときは初期表示をMasterのみにしたい場合は、UISplitViewControllerDelegate の splitViewController(_:collapseSecondary:onto:) で制御する。

Masterのみにしたい場合、false を返せばOK。
このメソッドは、iPhoneの初期表示だけでなく、サイズがRegular → Smallに変わった場合にも対応する。

ちなみにMasterのViewControllerにSplitViewのDelegateを設定したい場合、delegateの設定をviewLoadでしても間に合わないので、awakeFromNibで行う。(が、SplitView継承してカスタマイズする方がよいかもしれない)

Small → Regularに変わったときのDetailの指定


上記のコードでは、MasterのUINavigationViewControllerがTopのときは「なにも設定されていないView」をDetailに設定、それ以外はデフォルト = 表示されていたViewがDetailに表示 するようにしてみている。


当初は、「なにも設定されていないView」を使わない方法でアレコレ試していたが、いろんなパターンに対応するのは大変なので、使用する形の方が素直に実装できると思った。

Tabをデフォルトの挙動に合わせる

今回、Tabの中にSplitViewを入れる形で実装を進めているのだが、そうすると動かくなった機能がある。
  1. Hide Button Bar on Push (タブを非表示にする設定)
  2. TabItemタップ時、NavigationのTopに戻る
1については、setNavigationBarHiddenを使って自前で制御するしかない予感。iPhoneのときのみタブを非表示にしようかとも思ったが、無理に制御しないほうが無難かもと思い中。

2については、UITabBarControllerDelegate で以下のコードをいれてみた。

折りたたまれているときは、NavigationをRootに遷移。折りたたまれていない + Masterが非表示の場合には、Masterを表示する。(preferredDisplayModeにはallVisibleを設定している)

ちなみに、SplitViewのドキュメント
Split view controllers are normally installed at the root of your app’s window.
とあるので、Window Root以外にSplit Viewを使うのは推奨されていないかもしれない。


今回はここまで。
次回はSplitView以外のことを書いていこうと思う。
• • •