iOSアプリケーションでのプルアウトインターフェイスの実装

今日の記事では、スライドパネル、またはより単純に「カーテン」を使用してインターフェイスを操作するときに、Everyday Toolsの開発者が使用したトリックとツールについて説明します。 カーテンは、ユーザーが主にメイン画面と対話するが、定期的に追加情報にすばやくアクセスする必要があるアプリケーションに最適なソリューションです。











同僚の場合、インターフェイスはVehicle Location Trackerユーティリティ用に開発されました。このユーティリティでは、マークされた車両の位置を示すマップがメイン画面として使用され、プルアウトメニューによりユーザーは特定の位置で作業することができます。 さらに、プロセス全体がこのアプリケーションの例と見なされます。



Vehicle Location Trackerのカーテンの主な目的は、選択した駐車場に関する情報を表示することです。 ユーザーの当面のニーズに応じて、非表示にしたり、トップパネル(通常または詳細ビュー)の形でディスプレイに表示したり、編集ツールのセット全体を表示する完全に高度なものにしたりできます。



次のようになります。





タイプ別にビューを切り取り、それらを個別のXIBに分散させることから始めました。 さらに、要素は単にコンストラクターとして組み立てられました。 このタスクは、ビューの位置の順序を変更する可能性が暗示されておらず、一部の距離の変更のみであるという事実によって促進されました。



相互作用は、カーテンが置かれているメインコントローラーとそのコンテナーの間で構築されました。 メインコントローラーには、コンテナーコントローラーへのリンクがありました。 逆の相互作用は、デリゲートを介して実行されました。



カーテンのロジックは次のとおりです。 メインウィンドウには4つの状態があります。



-新しい駐車場の追加。

-既存のものの編集。

-専用駐車場を備えた通常の労働条件。

-何も選択されていない状態。



enum MapState : Int { case New case Edit case Normal case Empty }
      
      





カーテン自体には、はるかに多様な状態のセット(合計7つ)があります。 駐車場は、カーテン用に追加および編集された駐車場に分割されていませんが、基本モードに加えて、標準および編集モードにも拡張されたものがあります。 さらに、パラメーター「最後の固定位置」を持つ「移動中」状態が追加されます。



 indirect enum MenuState { case Empty case Hide(previous: MenuState) case Normal case Advanced case EditNormal case EditAdvanced case Motion(previous: MenuState) }
      
      





MapStateからMenuStateへの状態の転送は次のとおりです。



 class MapViewController: UIViewController { var currentState = StageMap.Zero { willSet { slideMenuVC.currentParentState = newValue } } } class SlideMenuViewController: UIViewController { var currentParentState = StageMap.Zero { willSet { switch newValue { case .Empty: updateVisibleOfViews(toState: .Empty) animateMove(toState: .Empty) case .Normal: updateVisibleOfViews(toState: .Normal) animateMove(toState: .Normal) case .New: updateVisibleOfViews(toState: .EditNormal) updateHeightOfTagsView() animateMove(toState: .EditNormal) case .Edit: updateVisibleOfViews(toState: .EditNormal) updateHeightOfTagsView() animateMove(toState: .EditNormal) } } } }
      
      





カーテン自体の移動は、UIView.animateWithDurationおよびCGAffineTransformMakeTranslationにより実行されました。



MenuStateのステータスを変更しても、MapStateには何の影響もありません(それに感謝します)。 メインコントローラーのMenuStateを変更することで状態が自動的に切り替えられ、カーテンに内部変更があります(UITapGestureRecognizerまたはUIPanGestureRecognizerを介して)。 さらに、「外部」で起こることは、デフォルトで、より高い優先度を持っています。



次に、カーテンの内部変更の作業について少し説明します。 認識エンジンを追加します。



  func addGesturesToView() { let tapGesture = UITapGestureRecognizer(target: self, action: #selector(SlideMenuViewController.tapGestureHandler(_:))) actionView.addGestureRecognizer(tapGesture) let panGesture = UIPanGestureRecognizer(target: self, action: #selector(SlideMenuViewController.panGestureHandler(_:))) actionView.addGestureRecognizer(panGesture) }
      
      





およびその実装:



  func tapGestureHandler(recognizer:UITapGestureRecognizer) { var state = MenuState.Hide var canMove = true switch currentState { case .Hide(previous: .Normal), .Hide(previous: .UpNormalAdvanced): toState = .Normal case .Hide(previous: .EditNormal), .Hide(previous: .EditAdvanced): toState = .EditNormal case .Normal: toState = .Hide(previous: .Normal) case .Advanced: toState = .Normal case .EditAdvanced: toState = .EditNormal case .EditNormal: toState = .EditAdvanced case .Motion: canMove = false default: break } updateVisibleOfViews(toState: state) if canMove { animateMove(toState: state) } } func panGestureHandler(recognizer:UIPanGestureRecognizer) { switch recognizer.state { case .Began: currentState = .Moved(previous: currentState) case .Changed: //    case .Ended, .Cancelled: //      .Moved(previous: lastState) //     ,         } }
      
      





プログラムの次の項目は、サイズと条件の計算です。 タグパネルと標準状態パネルにはフローティング高さがあります。したがって、ビュー要素のサイズを正確に計算するために、要求に応じて、コンテンツの量と量を考慮して目的の値が計算されました。



collectionViewコンテンツの高さ計算の例:



  func getContentHeight() -> CGFloat { let amountOfItems = tagCollectionView.numberOfItemsInSection(0) guard amountOfItems > 0 else { return kDefaultCollectionHeight } let indexPath = NSIndexPath(forItem: amountOfItems - 1, inSection: 0) guard let attributes = tagCollectionView.collectionViewLayout.layoutAttributesForItemAtIndexPath(indexPath) else { return kDefaultCollectionHeight } let collectionViewContentHeight = attributes.frame.origin.y + attributes.frame.size.height return collectionViewContentHeight }
      
      





すべてのサイズを手動で設定する必要はありません。一部の場所では、ソフトウェアを使用してプロセスを自動化しました。



Vehicle Location Trackerの作業では、 Sketchodeは非常に便利でした 。このツールはHabréでここ記述されています。 読んでいない人のために:私たちは、開発者が自分のニーズに合わせてSketchからレイアウトを調査し、「解析」できるようにするプログラムについて話します。 そしてオオカミはいっぱいで、デザイナーは落ち着いています。



Sketchodeは2つの点で役に立ちました。 まず、要素間の距離を正確かつ明確に示します。







第二に、要素のエクスポートは非​​常に便利に実装されます。 キャンバスの中央に立つはずの車両のアイコンは、同じビューで使用されていましたが、サイズが異なることが判明しました。 エクスポート中に直接寸法を設定できるため、アライメントの時間を大幅に節約できました。



インターフェイスに関しては、画面に表示されているように、アートボードで複数のウィンドウを開いて静かに切り替えることができるという利点があります。アートボードのリスト全体を駆け巡って、操作したばかりのものを探す必要はありません。







プルアウトパネルを使用する主な段階について検討しました。 最後に、作業時に便利になることがある、ささいなこと。 obj-cとswiftの両方で開発している間、開発者は時々、swiftでできる便利なことに感心します。 ここに、例えば:



 indirect enum MapButtonStage { case Disable(previous: MapButtonStage) case Off case On }
      
      





-考えられるすべてのボタンの状態が記述され、enum自体は、ボタンを強制的にブロックした場合、ボタンがどのモードであったかを記憶します。



 enum PinColor : Int { case Red case Violet case Green case Blue case Black case Yellow func getColor() -> UIColor { switch self { case .Violet: return UIColor.colorFromHexString("#8E44AD") case .Red: return UIColor.colorFromHexString("#FF3824") case .Green: return UIColor.colorFromHexString("#16A085") case .Blue: return UIColor.colorFromHexString("#0076FF") case .Black: return UIColor.colorFromHexString("#44464E") case .Yellow: return UIColor.colorFromHexString("#F5A623") } } var descriptionImage: String { switch self { case .Violet: return "_purple" case .Red: return "_red" case .Green: return "_green" case .Blue: return "_blue" case .Black: return "_grey" case .Yellow: return "_yellow" } } }
      
      









そして、ここで、一般に、美しさ:1つの列挙に、関連するUIColorと、アセットから必要な画像をロードするための名前の両方を配置しました。 もちろん、これらの名前をすべて1か所に保存できますが、新しい名前を追加すると不便で見苦しくなります。



名前のレイアウトに関する問題を回避するために、次の構造を作成します。



 struct ImageName { var color: PinColor var category: PinCategory func imageName() -> String { return category.descriptionImage + color.descriptionImage; } }
      
      





そしてそれを呼び出す:



  let name = ImageName(pinColor: color, pinCategory: category).imageName()
      
      





できた!



これらは、カーテンを作成する最初の経験で同僚が得たスキルです。 これらの観察が他の開発者に役立つことを願っています。 ご清聴ありがとうございました!



All Articles