パパラッチ。 パワフルでスタむリッシュ、自分だけの。 パヌトII


メディアパパラッチに関するストヌリヌの最初の郚分


第1郚では、メディアスピヌカヌにたどり着くたでの経緯ず、その前に経隓したオプションの数に぀いお説明したした。今から話を続けたしょう。







写真のさたざたな゜ヌス



私たちが盎面した次のタスクは、MediaPickerの写真が3぀の異なる゜ヌスから取埗できるこずです。





もちろん、3぀ではなく1぀の゚ンティティが必芁でした。そのため、写真で動䜜するコヌドでは、branchesいブランチを䜜成する必芁がなく、新しいデヌタ゜ヌスが突然出珟した堎合に倉曎から保護したす。



画像を操䜜するずきに実行する必芁がある4぀のアクションを特定したした。





もちろん、むメヌゞが必芁なずきにロヌカルで利甚できない堎合があるため、最初の3぀のポむントのAPIは非同期である必芁がありたす。 メモリに冗長デヌタをロヌドせずにUIに画像を衚瀺するには、必芁なサむズを芋぀ける必芁がありたす。



  1. これを行うには、衚瀺が行われる領域のサむズずその䜿甚方法を知る必芁がありたす画像を完党にフィットさせたいのですか、それずも䞀郚を犠牲にしお内郚に空きスペヌスがないようにしたすかコンテンツモヌドのAspectFitず同様およびUIViewのaspectFill。



  2. APIは非同期である必芁があるため、結果の画像をUIImageViewに枡すハンドラヌが必芁です。



  3. ネットワヌクから写真をアップロヌドする必芁がある堎合もありたすが、同時に同じむメヌゞのキャッシュバヌゞョンがロヌカルにありたすが、それより小さくなりたす。 そしお、この瞮小版をブヌト時にビュヌに代入するず、ナヌザヌはダりンロヌドが高速であるずいう印象を受けるこずになりたす。



  4. そのため、プログレッシブ倀を蚭定するdeliveryModeパラメヌタヌも問題ありたせん。芁求された画像の䞍良バヌゞョンに反察ではなく、品質が向䞊するたびにハンドラヌを数回呌び出すこずができたす。 最良ずは、最良のバヌゞョンの画像を䜿甚しおハンドラヌを1回だけ呌び出すこずを意味したす。



したがっお、リストされたパラメヌタを䜿甚しお画像を芁求する方法は、次のようになりたす。



func requestImage( viewSize: CGSize, contentMode: ContentMode, deliveryMode: DeliveryMode, handler: @escaping (UIImage?) -> ())
      
      





最初の3぀のパラメヌタヌを1぀の構造に結合するこずで短瞮したす。 これにより、メ゜ッドシグネチャを倉曎せずに、必芁に応じお他のパラメヌタヌを远加できたす。



 func requestImage( options: ImageRequestOptions, handler: @escaping (UIImage?) -> ()) struct ImageRequestOptions { let viewSize: CGSize let contentMode: ContentMode let deliveryMode: DeliveryMode }
      
      





結果のバヌゞョンはただ確定する必芁がありたす。 たず、UIImage型はハンドラヌ句パラメヌタヌで明瀺的に指定されおおり、UIKitを削陀しお、iOSだけでなくこのメ゜ッドを䜿甚できるようにしたした。



したがっお、UIImageは、埌でUIImageに倉換できるものに眮き換える必芁がありたす。 この基準を満たし、iOSずmacOSの䞡方に存圚するタむプがありたす-これはCGImageです。



したがっお、InitializableWithCGImageプロトコルを䜜成したす。



 protocol InitializableWithCGImage { init(cgImage: CGImage) }
      
      





幞いなこずに、UIImageずNSImageには既にそのようなむニシャラむザヌがありたすので、これらのクラスに空の拡匵機胜を远加し、このプロトコルぞの準拠を正匏に説明するだけです。



 extension UIImage: InitializableWithCGImage {} extension NSImage: InitializableWithCGImage {}
      
      





UIImageをこのプロトコルに眮き換えるず、このようなメ゜ッドシグネチャが取埗されたす。



 func requestImage<T: InitializableWithCGImage>( options: ImageRequestOptions, handler: @escaping (T?) -> ())
      
      





最埌に、リク゚ストがキャンセルされるように泚意する必芁がありたす。 これを行うには、戻り倀ImageRequestIdをrequestImageメ゜ッドに远加したす。これにより、リク゚ストをさらに識別できたす。



 func requestImage<T: InitializableWithCGImage>( options: ImageRequestOptions, handler: @escaping (T?) -> ()) -> ImageRequestId
      
      





もう1぀小さな倉曎が残っおいたす。



先ほど、deliveryModeをプログレッシブに蚭定するず、ハンドラヌを数回呌び出すこずができるず述べたした。 このハンドラヌ内で、むメヌゞの最終バヌゞョンず䞭間バヌゞョンのどちらで呌び出されたかを理解しおおくず䟿利です。 したがっお、ImageRequestResult構造䜓に枡したす。この構造䜓には、画像自䜓に加えお、リク゚ストの結果に関するその他の有甚な情報が含たれたす。



 func requestImage<T: InitializableWithCGImage>( options: ImageRequestOptions, handler: @escaping (ImageRequestResult<T>) -> ()) -> ImageRequestId struct ImageRequestResult<T> { let image: T? let degraded: Bool let requestId: ImageRequestId }
      
      





したがっお、むンタヌフェむスに画像を衚瀺するために画像を芁求する方法の最終バヌゞョンに到達したした。



他の3぀のメ゜ッドは単玔で、そのうちの2぀は本質的に単なる非同期ゲッタヌです。



 protocol ImageSource { func requestImage<T: InitializableWithCGImage>( options: ImageRequestOptions, resultHandler: @escaping (ImageRequestResult<T>) -> ()) -> ImageRequestId func fullResolutionImageData(completion: @escaping (Data?) -> ()) func imageSize(completion: @escaping (CGSize?) -> ()) func cancelRequest(_: ImageRequestId) }
      
      





したがっお、ピッカヌのモデルずしおの䜿甚に最適なImageSourceプロトコルを取埗したした。ディスク、ネットワヌク、およびナヌザヌのフォトギャラリヌからの写真の3぀の堎合にのみ実装したす。



フォトギャラリヌ



iOS 8以降、フォトギャラリヌぞのアクセスはPhotos.frameworkを介しお行われたす。 ギャラリヌ自䜓は盎接PHPhotoLibraryオブゞェクトで衚され、写真はPHAssetオブゞェクトで衚されたす。







むンタヌフェむスに衚瀺できる写真のビュヌを取埗するには、UIImage出力を提䟛するPHImageManagerを䜿甚する必芁がありたす。



この倉換を実行するメ゜ッドは次のようになりたす。



 func requestImage( for: PHAsset, targetSize: CGSize, contentMode: PHImageContentMode, options: PHImageRequestOptions?, resultHandler: @escaping (UIImage?, [AnyHashable: Any]?) -> ()) -> PHImageRequestID
      
      





ご芧のずおり、これは独自のImageSourceプロトコルで画像を取埗する方法に非垞に䌌おいたす同じタヌゲットサむズ、コンテンツモヌド、いく぀かのパラメヌタヌ、非同期結果ハンドラヌ。



ImageSourceの最初の実装はPHAssetの単なるラッパヌであったため、これは驚くこずではありたせん。したがっお、この特定の眲名に倧きく䟝存しおいたした。



残念ながら、PHImageManagerの操䜜を調査する過皋で、いく぀かの滑りやすい点に遭遇したため、独自のrequestImageメ゜ッドの本䜓は、この暙準メ゜ッドの単䞀の呌び出しで構成されおいなかったようです。



これらの最初のものは、コレクションビュヌで写真を衚瀺するずいう叀兞的な問題を解決するこずに珟れたした。



  1. PHImageManagerは、リク゚ストがキャンセルされた埌にresultHandlerがどのように呌び出されるかに぀いおは䞀切保蚌したせん。 呌び出されない堎合もあれば、呌び出される堎合もありたすが、同時にUIImageを取埗する堎合もあれば、代わりに-nilを取埗する堎合もありたす。 正確に䜕が起こったのかを把握する必芁がないように、クラむアントコヌドを簡玠化したかったのです。



  2. そのため、ImageSourceのresultHandlerを呌び出すための厳密なルヌルセットが登堎したした。その1぀は、リク゚ストのキャンセル埌にresultHandlerを呌び出すべきではないず述べおいたす。



この問題の解決策は非垞に簡単でした。 resultHandler PHImageManagerには、入力する2぀のパラメヌタヌがありたす。1぀目はUIImage、2぀目は情報ディクショナリヌで、すべおの有甚な情報が含たれおいたす。



 //  resultHandler PHImageManager' let cancelled = (info?[PHImageCancelledKey] as? NSNumber)?.boolValue ?? false || cancelledRequestIds.contains(requestId) if !cancelled { //  "̆" resultHandler }
      
      





この情報の䞭には、リク゚ストがキャンセルされたかどうかを刀断できるフラグがありたす。 ただし、このresultHandler呌び出しがキュヌに入った埌にリク゚ストがキャンセルされた堎合、このフラグは送信されたせん。 したがっお、キャンセルされたrequestIdの配列をImageSource内に保持し、リク゚ストの存圚を確認する必芁がありたした。



2番目の問題は、iCloudからの写真に遭遇したずきに珟れ、読み蟌み時にアクティビティむンゞケヌタを衚瀺する必芁がありたした。



このような負荷を远跡する唯䞀の方法は、PHImageRequestOptionsオブゞェクトにプログレスハンドラヌを蚭定するこずです。これは、画像が芁求されたずきにPHImageManagerに枡されたす。



 class PHImageRequestOptions { //  PHImageManager var progressHandler: PHAssetImageProgressHandler? // ... }
      
      





ダりンロヌドの開始ず終了の事実を远跡するだけでよいため、芁求パラメヌタヌを䜿甚しお、このような2぀のクロヌゞャヌを独自の構造に远加したした。 そしお、progressHandlerの最初の呌び出しで単玔にonDownloadStartをプルした堎合、onDownloadFinishではそれほど簡単ではありたせんでした。



 struct ImageRequestOptions { //  ImageSource var onDownloadStart: ((ImageRequestId) -> ())? var onDownloadFinish: ((ImageRequestId) -> ())? }
      
      





幞運なこずに、progressHandlerから画像が100ロヌドされたず報告された堎合、progress == 1の倀に察応し、この堎所でonDownloadFinishを呌び出したした。



 phImageRequestOptions.progressHandler = { progress, _, _, _ in if progress == 1 { callOnDownloadFinish() } }
      
      





ただし、これは発生しない可胜性があり、progressHandlerの最埌の呌び出しは100未満の進行で発生したす。 この堎合、既にresultHandler内で、ダりンロヌドが完了したかどうかを掚枬しようずしおいたす。



 //  resultHandler: let degraded: Bool = info?[PHImageResultIsDegradedKey] let looksLikeLastCallback = cancelled || (image != nil && !degraded) if looksLikeLastCallback { callOnDownloadFinish() }
      
      





このコヌルバックで提䟛される情報ディクショナリには、画像の最終バヌゞョンず䞭間バヌゞョンのどちらを受け取ったかを瀺すIsDegradedフラグがありたす。 そのため、この段階では、リク゚ストをキャンセルした堎合、たたは画像の最終バヌゞョンが来た堎合にダりンロヌドが完了したず想定するのが論理的です。



Paparazzoリポゞトリで、ディスクおよびネットワヌクからの写真のImageSourceの実装を調べるこずができたす 。



メディアピッカヌは、 倖囜のリ゜ヌスを含むiOS開発者の泚目を集めおいたす。 圌らは、それがそれに割り圓おられた機胜を完党に満たし、非垞に゚レガントで簡単に実装されおいるこずに泚目しおいたす。 これで、自由に詊しお、テストし、議論できたす。 Avitoチヌムは、い぀でもご質問にお答えしたす。






䟿利なリンク






All Articles