前言
iOS下類似的圖片瀏覽器不管是OC版本還是Swift版本目前已經(jīng)開源了不少恋腕。但是作為一個六七年的老iOS開發(fā)者,以及自己曾經(jīng)積累了的不少社交App經(jīng)驗焰手,還是忍不住基于自己的想法以及目前項目中類似的組件重新擼了一個糟描。毫無疑問,此次開源的JFHeroBrowser书妻,首選語言是Swift(完全Swift不包含任何OC代碼)蚓挤,偏向更Swifty的方式-面向協(xié)議處理數(shù)據(jù)模型,還有Swift進階枚舉用法驻子,命名空間等灿意,如果你想深入學(xué)習(xí)Swift,我相信本組件會讓你有不同的體驗,另外由于樓主同時也在開發(fā)Flutter,編碼方式上也是更"響應(yīng)式"崇呵。而且與大多數(shù)三方庫內(nèi)置ImageCache(大多是SDWebImage)不同缤剧,本組件,不包含內(nèi)置的ImageCache,但是如果您集成了本項目作為圖片瀏覽域慷,網(wǎng)絡(luò)圖這塊荒辕,您需要自行實現(xiàn)HeroNetworkImageProvider協(xié)議,可以使用Kingfisher或SDWebImage抑或是你項目中自行設(shè)計的圖片緩存汗销,完美解決組件耦合問題,具體使用參考下面用法抵窒。另外本組件支持多種資源格式弛针,如本地圖(UIImage),網(wǎng)絡(luò)圖(url),data(二進制),視頻(url)李皇,甚至你自行實現(xiàn)ImageVM也可以接入你想要的資源削茁。話不多說,我們來看具體使用方式掉房。
下載安裝地址
cocoaPods:
pod 'JFHeroBrowser', '1.3.2'
github:
https://github.com/JerryFans/JFHeroBrowser
Usage
首先初始化配置
如上面所說茧跋,在Appdelegate didFinish處自行接入HeroNetworkImageProvider。實現(xiàn)func downloadImage(with imgUrl: String, complete: Complete<UIImage>?)
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<UIImage>?) {
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<UIImage>?) {
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支持轉(zhuǎn)二進制,或者flutter的Uin8List)
list.append(HeroBrowserDataImageViewModule(data: imageSource[i]))
//網(wǎng)絡(luò)圖 也是最常用場景
list.append(HeroBrowserNetworkImageViewModule(thumbailImgUrl: thumbs[i], originImgUrl: origins[i]))
然后是具體使用示例
瀏覽圖片
self是當(dāng)前控制器恤左,寫了一個hero的命名空間贴唇,viewModules就是上面一個個定義的viewModule示例,支持視頻或者不同圖片VM混搭也是可以的飞袋。
另外支持參數(shù):
- pageControlType (默認(rèn)pageControl或者數(shù)字1/5類似)
- heroView (也就是你要放大縮放那個imageView,如果不填就不會有縮放的效果戳气,就是一個alpha漸變)
- heroBrowserDidLongPressHandle (長按回調(diào),可以做些保存圖片巧鸭、分享等動作)
- imageDidChangeHandle (切換圖片后瓶您,上個頁面的imageView也要切換,才可以dissmiss回到相應(yīng)位置纲仍,如果不設(shè)置就是alpha效果)
- enableBlurEffect 是否開啟毛玻璃背景呀袱,默認(rèn)開啟,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轉(zhuǎn)換成SwiftUI的寫法,但實現(xiàn)中發(fā)現(xiàn)不少問題乡革,特別是轉(zhuǎn)場效果無從下手寇僧。如果要純SwiftUI代碼實現(xiàn)摊腋,看來只能使用SwiftUI布局的方式重寫,期待之后有空可以做個嘗試嘁傀。但是實際上兴蒸,HeroBrowser是通過modal的方式進場的,我們直接獲取rootViewController直接跳轉(zhuǎn)亦可细办,但是就是缺少縮放動畫橙凳,使用了默認(rèn)的alpha轉(zhuǎn)場,代碼如下蟹腾。本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 跳轉(zhuǎn)瀏覽
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)
}
}
}
附件地址
- 項目GitHub地址: JFHeroBrowser
- Example https://github.com/JerryFans/JFHeroBrowser/tree/master/Example
- SwiftUIExample https://github.com/JerryFans/JFHeroBrowser/tree/master/SwiftUIExample