支持SwiftUI!Swift版圖片&視頻瀏覽器-JFHeroBrowser上線啦

語言: CN / TW / HK

前言

我正在參與掘金技術社區創作者簽約計劃招募活動,點擊鏈接報名投稿

iOS下類似的圖片瀏覽器不管是OC版本還是Swift版本目前已經開源了不少。但是作為一個六七年的老iOS開發者,以及自己曾經積累了的不少社交App經驗,還是忍不住基於自己的想法以及目前項目中類似的組件重新擼了一個。毫無疑問,此次開源的JFHeroBrowser,首選語言是Swift(完全Swift不包含任何OC代碼),偏向更Swifty的方式-面向協議處理數據模型,還有Swift進階枚舉用法,命名空間等,如果你想深入學習Swift,我相信本組件會讓你有不同的體驗,另外由於樓主同時也在開發Flutter,編碼方式上也是更"響應式"。而且與大多數三方庫內置ImageCache(大多是SDWebImage)不同,本組件,不包含內置的ImageCache,但是如果您集成了本項目作為圖片瀏覽,網絡圖這塊,您需要自行實現HeroNetworkImageProvider協議,可以使用Kingfisher或SDWebImage亦或是你項目中自行設計的圖片緩存,完美解決組件耦合問題,具體使用參考下面用法。另外本組件支持多種資源格式,如本地圖(UIImage),網絡圖(url),data(二進制),視頻(url),甚至你自行實現ImageVM也可以接入你想要的資源。話不多説,我們來看具體使用方式。

下載安裝地址

cocoaPods:

```

pod 'JFHeroBrowser', '1.3.2'

```

github:

https://github.com/JerryFans/JFHeroBrowser

Usage

首先初始化配置

如上面所説,在Appdelegate didFinish處自行接入HeroNetworkImageProvider。實現func downloadImage(with imgUrl: String, complete: Complete?)

```

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {

JFHeroBrowserGlobalConfig.default.networkImageProvider = HeroNetworkImageProvider.shared

// JFHeroBrowserGlobalConfig.default.networkImageProvider = SDWebImageNetworkImageProvider.shared

return true

}

```

Kingfisher參考

```

extension HeroNetworkImageProvider: NetworkImageProvider {

func downloadImage(with imgUrl: String, complete: Complete?) {

KingfisherManager.shared.retrieveImage(with: URL(string: imgUrl)!, options: nil) { receiveSize, totalSize in

guard totalSize > 0 else { return }

let progress:CGFloat = CGFloat(CGFloat(receiveSize) / CGFloat(totalSize))

complete?(.progress(progress))

} downloadTaskUpdated: { task in

} completionHandler: { result in

switch result {

case .success(let loadingImageResult):

complete?(.success(loadingImageResult.image))

break

case .failure(let error):

complete?(.failed(error))

break

}

}

}

}

class HeroNetworkImageProvider: NSObject {

@objc static let shared = HeroNetworkImageProvider()

}

```

SDWebImage參考

```

extension SDWebImageNetworkImageProvider: NetworkImageProvider {

func downloadImage(with imgUrl: String, complete: Complete?) {

SDWebImageManager.shared.loadImage(with: URL(string: imgUrl)) { receiveSize, totalSize, url in

guard totalSize > 0 else { return }

let progress:CGFloat = CGFloat(CGFloat(receiveSize) / CGFloat(totalSize))

complete?(.progress(progress))

} completed: { image, data, error, _, isfinished, url in

if let error = error {

complete?(.failed(error))

} else if let image = image {

complete?(.success(image))

} else {

complete?(.failed(nil))

}

}

}

}

class SDWebImageNetworkImageProvider: NSObject {

@objc static let shared = SDWebImageNetworkImageProvider()

}

```

然後定義你需要瀏覽的ViewModule

目前支持HeroBrowserNetworkImageViewModule、HeroBrowserDataImageViewModule、HeroBrowserLocalImageViewModule、HeroBrowserVideoViewModule四種類型ViewModule。理論上還可以定義AssetImageViewModule(支持從相冊瀏覽圖片),在我另外一個未開源的相冊組件裏面使用了,所以ViewModule的擴展非常方便使用者去擴展各種各樣的場景,而且單一場景,由於某些特定場景比較不場景,我這隻提供幾種常用的場景。

幾種ViewModule代碼示例

```

//視頻

let vm1 = HeroBrowserVideoViewModule(thumbailImgUrl: "http://image.jerryfans.com/bf.jpg", fileUrlPath: path, provider: HeroNetworkImageProvider.shared, autoPlay: false)

list.append(vm1)

//本地圖(UIImage)

list.append(HeroBrowserLocalImageViewModule(image: img))

//data圖 (file Image支持轉二進制,或者flutter的Uin8List)

list.append(HeroBrowserDataImageViewModule(data: imageSource[i]))

//網絡圖 也是最常用場景

list.append(HeroBrowserNetworkImageViewModule(thumbailImgUrl: thumbs[i], originImgUrl: origins[i]))

```

然後是具體使用示例

瀏覽圖片

self是當前控制器,寫了一個hero的命名空間,viewModules就是上面一個個定義的viewModule示例,支持視頻或者不同圖片VM混搭也是可以的。

另外支持參數:

  • pageControlType (默認pageControl或者數字1/5類似)

  • heroView (也就是你要放大縮放那個imageView,如果不填就不會有縮放的效果,就是一個alpha漸變)

  • heroBrowserDidLongPressHandle (長按回調,可以做些保存圖片、分享等動作)

  • imageDidChangeHandle (切換圖片後,上個頁面的imageView也要切換,才可以dissmiss回到相應位置,如果不設置就是alpha效果)

  • enableBlurEffect 是否開啟毛玻璃背景,默認開啟,false就是黑色背景。

```

self.hero.browserPhoto(viewModules: list, initIndex: indexPath.item) {

[

.pageControlType(.pageControl),

.heroView(cell.imageView),

.heroBrowserDidLongPressHandle({ [weak self] heroBrowser,vm in

self?.longPressHandle(vm: vm)

}),

.imageDidChangeHandle({ [weak self] imageIndex in

guard let self = self else { return nil }

guard let cell = self.collectionView.cellForItem(at: IndexPath(item: imageIndex, section: 0)) as? NetworkImageCollectionViewCell else { return nil }

let rect = cell.convert(cell.imageView.frame, to: self.view)

if self.view.frame.contains(rect) {

return cell.imageView

}

return nil

})

]

}

```

瀏覽單個視頻

效果:

```

let vm = HeroBrowserVideoViewModule(thumbailImgUrl: "http://image.jerryfans.com/w_720_h_1280_d_41_89fd26217dc299a442363581deb75b90_iOS_0.jpg", videoUrl: "http://image.jerryfans.com/w_720_h_1280_d_41_2508b8aa06a2e30d2857f9bcbdfd1de0_iOS.mp4", provider: HeroNetworkImageProvider.shared, autoPlay: true)

self.hero.browserVideo(viewModule: vm)

```

瀏覽混合資源(圖片+視頻,或多個視頻)

```

lazy var list: [HeroBrowserViewModuleBaseProtocol] = {

var list: [HeroBrowserViewModuleBaseProtocol] = []

let vm = HeroBrowserVideoViewModule(thumbailImgUrl: "http://image.jerryfans.com/w_720_h_1280_d_41_89fd26217dc299a442363581deb75b90_iOS_0.jpg", videoUrl: "http://image.jerryfans.com/w_720_h_1280_d_41_2508b8aa06a2e30d2857f9bcbdfd1de0_iOS.mp4", provider: HeroNetworkImageProvider.shared, autoPlay: true)

list.append(vm)

list.append(HeroBrowserLocalImageViewModule(image: UIImage(named: "template-1")!))

if let path = Bundle.main.path(forResource: "bf.MOV", ofType: nil) {

let vm1 = HeroBrowserVideoViewModule(thumbailImgUrl: "http://image.jerryfans.com/bf.jpg", fileUrlPath: path, provider: HeroNetworkImageProvider.shared, autoPlay: false)

list.append(vm1)

}

return list

}()

self.hero.browserMultiSoures(viewModules: self.list, initIndex: 1) {

[

.enableBlurEffect(false),

.heroView(button.imageView),

.imageDidChangeHandle({ [weak self] imageIndex in

guard let self = self else { return nil }

guard let btn = self.view.viewWithTag(imageIndex) as? UIButton else { return nil }

return btn.imageView

})

]

}

```

SwiftUI的支持

一開始的想法是通過官方的UIViewController轉換成SwiftUI的寫法,但實現中發現不少問題,特別是轉場效果無從下手。如果要純SwiftUI代碼實現,看來只能使用SwiftUI佈局的方式重寫,期待之後有空可以做個嘗試。但是實際上,HeroBrowser是通過modal的方式進場的,我們直接獲取rootViewController直接跳轉亦可,但是就是缺少縮放動畫,使用了默認的alpha轉場,代碼如下。本demo也提交到github了,有需要可以查閲。

配置初始化代碼

```

class AppDelegate: NSObject, UIApplicationDelegate {

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool {

JFHeroBrowserGlobalConfig.default.networkImageProvider = HeroNetworkImageProvider.shared

return true

}

}

@main

struct SwiftUIExampleApp: App {

@UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate

var body: some Scene {

WindowGroup {

ContentView()

}

}

}

```

```

//獲取頂層vc

let keyWindow = UIApplication.shared.connectedScenes

.map({ $0 as? UIWindowScene })

.compactMap({ $0 })

.first?.windows.first

let myAppRootVC : UIViewController? = keyWindow?.rootViewController

//從一個圖片 GridView 跳轉瀏覽

LazyVGrid(columns: columns) {

ForEach(1..<origins.count, id:.self) { index in

ImageCell(index: index).frame(height: columns.count == 1 ? 300 : 150).onTapGesture {

var list: [HeroBrowserViewModule] = []

for i in 0..<origins.count {

list.append(HeroBrowserNetworkImageViewModule(thumbailImgUrl: thumbs[i], originImgUrl: origins[i]))

}

myAppRootVC?.hero.browserPhoto(viewModules: list, initIndex: index)

}

}

}

```

附件地址

末尾

我正在參與掘金技術社區創作者簽約計劃招募活動,點擊鏈接報名投稿