initial
This commit is contained in:
70
Pods/ZLPhotoBrowser/Sources/General/ZLAddPhotoCell.swift
generated
Normal file
70
Pods/ZLPhotoBrowser/Sources/General/ZLAddPhotoCell.swift
generated
Normal file
@@ -0,0 +1,70 @@
|
||||
//
|
||||
// ZLAddPhotoCell.swift
|
||||
// ZLPhotoBrowser
|
||||
//
|
||||
// Created by ruby109 on 2020/11/3.
|
||||
//
|
||||
// Copyright (c) 2020 Long Zhang <495181165@qq.com>
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
// THE SOFTWARE.
|
||||
|
||||
import UIKit
|
||||
import Foundation
|
||||
|
||||
class ZLAddPhotoCell: UICollectionViewCell {
|
||||
|
||||
private lazy var imageView: UIImageView = {
|
||||
let view = UIImageView(image: .zl.getImage("zl_addPhoto"))
|
||||
view.contentMode = .scaleAspectFit
|
||||
view.clipsToBounds = true
|
||||
return view
|
||||
}()
|
||||
|
||||
override init(frame: CGRect) {
|
||||
super.init(frame: frame)
|
||||
setupUI()
|
||||
}
|
||||
|
||||
deinit {
|
||||
zl_debugPrint("ZLAddPhotoCell deinit")
|
||||
}
|
||||
|
||||
@available(*, unavailable)
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
override func layoutSubviews() {
|
||||
super.layoutSubviews()
|
||||
|
||||
imageView.frame = CGRect(x: 0, y: 0, width: bounds.width / 3, height: bounds.width / 3)
|
||||
imageView.center = CGPoint(x: bounds.midX, y: bounds.midY)
|
||||
}
|
||||
|
||||
func setupUI() {
|
||||
if ZLPhotoUIConfiguration.default().cellCornerRadio > 0 {
|
||||
layer.masksToBounds = true
|
||||
layer.cornerRadius = ZLPhotoUIConfiguration.default().cellCornerRadio
|
||||
}
|
||||
|
||||
backgroundColor = .zl.cameraCellBgColor
|
||||
contentView.addSubview(imageView)
|
||||
}
|
||||
|
||||
}
|
||||
208
Pods/ZLPhotoBrowser/Sources/General/ZLAlbumListCell.swift
generated
Normal file
208
Pods/ZLPhotoBrowser/Sources/General/ZLAlbumListCell.swift
generated
Normal file
@@ -0,0 +1,208 @@
|
||||
//
|
||||
// ZLAlbumListCell.swift
|
||||
// ZLPhotoBrowser
|
||||
//
|
||||
// Created by long on 2020/8/19.
|
||||
//
|
||||
// Copyright (c) 2020 Long Zhang <495181165@qq.com>
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
// THE SOFTWARE.
|
||||
|
||||
import UIKit
|
||||
|
||||
class ZLAlbumListCell: UITableViewCell {
|
||||
|
||||
private lazy var coverImageView: UIImageView = {
|
||||
let view = UIImageView()
|
||||
view.contentMode = .scaleAspectFill
|
||||
view.clipsToBounds = true
|
||||
if ZLPhotoUIConfiguration.default().cellCornerRadio > 0 {
|
||||
view.layer.masksToBounds = true
|
||||
view.layer.cornerRadius = ZLPhotoUIConfiguration.default().cellCornerRadio
|
||||
}
|
||||
return view
|
||||
}()
|
||||
|
||||
private lazy var titleLabel: UILabel = {
|
||||
let label = UILabel()
|
||||
label.font = .zl.font(ofSize: 17)
|
||||
label.textColor = .zl.albumListTitleColor
|
||||
return label
|
||||
}()
|
||||
|
||||
private lazy var countLabel: UILabel = {
|
||||
let label = UILabel()
|
||||
label.font = .zl.font(ofSize: 16)
|
||||
label.textColor = .zl.albumListCountColor
|
||||
return label
|
||||
}()
|
||||
|
||||
private var imageIdentifier: String?
|
||||
|
||||
private var model: ZLAlbumListModel!
|
||||
|
||||
private var style: ZLPhotoBrowserStyle = .embedAlbumList
|
||||
|
||||
private var indicator: UIImageView = {
|
||||
var image = UIImage.zl.getImage("zl_ablumList_arrow")
|
||||
if isRTL() {
|
||||
image = image?.imageFlippedForRightToLeftLayoutDirection()
|
||||
}
|
||||
|
||||
let view = UIImageView(image: image)
|
||||
view.contentMode = .scaleAspectFit
|
||||
return view
|
||||
}()
|
||||
|
||||
lazy var selectBtn: UIButton = {
|
||||
let btn = UIButton(type: .custom)
|
||||
btn.isUserInteractionEnabled = false
|
||||
btn.isHidden = true
|
||||
btn.setImage(.zl.getImage("zl_albumSelect"), for: .selected)
|
||||
return btn
|
||||
}()
|
||||
|
||||
override func awakeFromNib() {
|
||||
super.awakeFromNib()
|
||||
// Initialization code
|
||||
}
|
||||
|
||||
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
|
||||
super.init(style: style, reuseIdentifier: reuseIdentifier)
|
||||
setupUI()
|
||||
}
|
||||
|
||||
@available(*, unavailable)
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
override func layoutSubviews() {
|
||||
super.layoutSubviews()
|
||||
|
||||
let width = contentView.zl.width
|
||||
let height = contentView.zl.height
|
||||
|
||||
let coverImageW = height - 4
|
||||
let maxTitleW = width - coverImageW - 80
|
||||
|
||||
var titleW: CGFloat = 0
|
||||
var countW: CGFloat = 0
|
||||
if let model = model {
|
||||
titleW = min(
|
||||
bounds.width / 3 * 2,
|
||||
model.title.zl.boundingRect(
|
||||
font: .zl.font(ofSize: 17),
|
||||
limitSize: CGSize(width: CGFloat.greatestFiniteMagnitude, height: 30)
|
||||
).width
|
||||
)
|
||||
titleW = min(titleW, maxTitleW)
|
||||
|
||||
countW = ("(" + String(model.count) + ")").zl
|
||||
.boundingRect(
|
||||
font: .zl.font(ofSize: 16),
|
||||
limitSize: CGSize(width: CGFloat.greatestFiniteMagnitude, height: 30)
|
||||
).width
|
||||
}
|
||||
|
||||
if isRTL() {
|
||||
let imageViewX: CGFloat
|
||||
if style == .embedAlbumList {
|
||||
imageViewX = width - coverImageW
|
||||
} else {
|
||||
imageViewX = width - coverImageW - 12
|
||||
}
|
||||
|
||||
coverImageView.frame = CGRect(x: imageViewX, y: 2, width: coverImageW, height: coverImageW)
|
||||
titleLabel.frame = CGRect(
|
||||
x: coverImageView.zl.left - titleW - 10,
|
||||
y: (height - 30) / 2,
|
||||
width: titleW,
|
||||
height: 30
|
||||
)
|
||||
|
||||
countLabel.frame = CGRect(
|
||||
x: titleLabel.zl.left - countW - 10,
|
||||
y: (height - 30) / 2,
|
||||
width: countW,
|
||||
height: 30
|
||||
)
|
||||
selectBtn.frame = CGRect(x: 20, y: (height - 20) / 2, width: 20, height: 20)
|
||||
indicator.frame = CGRect(x: 20, y: (bounds.height - 15) / 2, width: 15, height: 15)
|
||||
return
|
||||
}
|
||||
|
||||
let imageViewX: CGFloat
|
||||
if style == .embedAlbumList {
|
||||
imageViewX = 0
|
||||
} else {
|
||||
imageViewX = 12
|
||||
}
|
||||
|
||||
coverImageView.frame = CGRect(x: imageViewX, y: 2, width: coverImageW, height: coverImageW)
|
||||
titleLabel.frame = CGRect(
|
||||
x: coverImageView.zl.right + 10,
|
||||
y: (bounds.height - 30) / 2,
|
||||
width: titleW,
|
||||
height: 30
|
||||
)
|
||||
countLabel.frame = CGRect(x: titleLabel.zl.right + 10, y: (height - 30) / 2, width: countW, height: 30)
|
||||
selectBtn.frame = CGRect(x: width - 20 - 20, y: (height - 20) / 2, width: 20, height: 20)
|
||||
indicator.frame = CGRect(x: width - 20 - 15, y: (height - 15) / 2, width: 15, height: 15)
|
||||
}
|
||||
|
||||
func setupUI() {
|
||||
backgroundColor = .zl.albumListBgColor
|
||||
selectionStyle = .none
|
||||
accessoryType = .none
|
||||
|
||||
contentView.addSubview(coverImageView)
|
||||
contentView.addSubview(titleLabel)
|
||||
contentView.addSubview(countLabel)
|
||||
contentView.addSubview(selectBtn)
|
||||
contentView.addSubview(indicator)
|
||||
}
|
||||
|
||||
func configureCell(model: ZLAlbumListModel, style: ZLPhotoBrowserStyle) {
|
||||
self.model = model
|
||||
self.style = style
|
||||
|
||||
titleLabel.text = self.model.title
|
||||
countLabel.text = "(" + String(self.model.count) + ")"
|
||||
|
||||
if style == .embedAlbumList {
|
||||
selectBtn.isHidden = false
|
||||
indicator.isHidden = true
|
||||
} else {
|
||||
indicator.isHidden = false
|
||||
selectBtn.isHidden = true
|
||||
}
|
||||
|
||||
imageIdentifier = self.model.headImageAsset?.localIdentifier
|
||||
if let asset = self.model.headImageAsset {
|
||||
let w = bounds.height * 2.5
|
||||
ZLPhotoManager.fetchImage(for: asset, size: CGSize(width: w, height: w)) { [weak self] image, _ in
|
||||
if self?.imageIdentifier == self?.model.headImageAsset?.localIdentifier {
|
||||
self?.coverImageView.image = image ?? .zl.getImage("zl_defaultphoto")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
158
Pods/ZLPhotoBrowser/Sources/General/ZLAlbumListController.swift
generated
Normal file
158
Pods/ZLPhotoBrowser/Sources/General/ZLAlbumListController.swift
generated
Normal file
@@ -0,0 +1,158 @@
|
||||
//
|
||||
// ZLAlbumListController.swift
|
||||
// ZLPhotoBrowser
|
||||
//
|
||||
// Created by long on 2020/8/18.
|
||||
//
|
||||
// Copyright (c) 2020 Long Zhang <495181165@qq.com>
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
// THE SOFTWARE.
|
||||
|
||||
import UIKit
|
||||
import Photos
|
||||
|
||||
class ZLAlbumListController: UIViewController {
|
||||
|
||||
private lazy var navView = ZLExternalAlbumListNavView(title: localLanguageTextValue(.photo))
|
||||
|
||||
private var navBlurView: UIVisualEffectView?
|
||||
|
||||
private lazy var tableView: UITableView = {
|
||||
let view = UITableView(frame: .zero, style: .plain)
|
||||
view.backgroundColor = .zl.albumListBgColor
|
||||
view.tableFooterView = UIView()
|
||||
view.rowHeight = 65
|
||||
view.separatorInset = UIEdgeInsets(top: 0, left: 12, bottom: 0, right: 0)
|
||||
view.separatorColor = .zl.separatorLineColor
|
||||
view.delegate = self
|
||||
view.dataSource = self
|
||||
|
||||
if #available(iOS 11.0, *) {
|
||||
view.contentInsetAdjustmentBehavior = .always
|
||||
}
|
||||
|
||||
ZLAlbumListCell.zl.register(view)
|
||||
return view
|
||||
}()
|
||||
|
||||
private var arrDataSource: [ZLAlbumListModel] = []
|
||||
|
||||
private var shouldReloadAlbumList = true
|
||||
|
||||
override var preferredStatusBarStyle: UIStatusBarStyle {
|
||||
return ZLPhotoUIConfiguration.default().statusBarStyle
|
||||
}
|
||||
|
||||
deinit {
|
||||
zl_debugPrint("ZLAlbumListController deinit")
|
||||
}
|
||||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
|
||||
setupUI()
|
||||
PHPhotoLibrary.shared().register(self)
|
||||
}
|
||||
|
||||
override func viewWillAppear(_ animated: Bool) {
|
||||
super.viewWillAppear(animated)
|
||||
navigationController?.navigationBar.isHidden = true
|
||||
|
||||
guard shouldReloadAlbumList else {
|
||||
return
|
||||
}
|
||||
|
||||
DispatchQueue.global().async {
|
||||
ZLPhotoManager.getPhotoAlbumList(ascending: ZLPhotoConfiguration.default().sortAscending, allowSelectImage: ZLPhotoConfiguration.default().allowSelectImage, allowSelectVideo: ZLPhotoConfiguration.default().allowSelectVideo) { [weak self] albumList in
|
||||
self?.arrDataSource.removeAll()
|
||||
self?.arrDataSource.append(contentsOf: albumList)
|
||||
|
||||
self?.shouldReloadAlbumList = false
|
||||
ZLMainAsync {
|
||||
self?.tableView.reloadData()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override func viewDidLayoutSubviews() {
|
||||
super.viewDidLayoutSubviews()
|
||||
|
||||
let navViewNormalH: CGFloat = 44
|
||||
|
||||
var insets = UIEdgeInsets(top: 20, left: 0, bottom: 0, right: 0)
|
||||
var collectionViewInsetTop: CGFloat = 20
|
||||
if #available(iOS 11.0, *) {
|
||||
insets = view.safeAreaInsets
|
||||
collectionViewInsetTop = navViewNormalH
|
||||
} else {
|
||||
collectionViewInsetTop += navViewNormalH
|
||||
}
|
||||
|
||||
navView.frame = CGRect(x: 0, y: 0, width: view.frame.width, height: insets.top + navViewNormalH)
|
||||
|
||||
tableView.frame = CGRect(x: insets.left, y: 0, width: view.frame.width - insets.left - insets.right, height: view.frame.height)
|
||||
tableView.contentInset = UIEdgeInsets(top: collectionViewInsetTop, left: 0, bottom: 0, right: 0)
|
||||
tableView.scrollIndicatorInsets = UIEdgeInsets(top: 44, left: 0, bottom: 0, right: 0)
|
||||
}
|
||||
|
||||
private func setupUI() {
|
||||
view.backgroundColor = .zl.albumListBgColor
|
||||
|
||||
view.addSubview(tableView)
|
||||
|
||||
navView.backBtn.isHidden = true
|
||||
navView.cancelBlock = { [weak self] in
|
||||
let nav = self?.navigationController as? ZLImageNavController
|
||||
nav?.cancelBlock?()
|
||||
nav?.dismiss(animated: true, completion: nil)
|
||||
}
|
||||
view.addSubview(navView)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extension ZLAlbumListController: UITableViewDataSource, UITableViewDelegate {
|
||||
|
||||
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
|
||||
return arrDataSource.count
|
||||
}
|
||||
|
||||
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
|
||||
let cell = tableView.dequeueReusableCell(withIdentifier: ZLAlbumListCell.zl.identifier, for: indexPath) as! ZLAlbumListCell
|
||||
|
||||
cell.configureCell(model: arrDataSource[indexPath.row], style: .externalAlbumList)
|
||||
|
||||
return cell
|
||||
}
|
||||
|
||||
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
|
||||
let vc = ZLThumbnailViewController(albumList: arrDataSource[indexPath.row])
|
||||
show(vc, sender: nil)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extension ZLAlbumListController: PHPhotoLibraryChangeObserver {
|
||||
|
||||
func photoLibraryDidChange(_ changeInstance: PHChange) {
|
||||
shouldReloadAlbumList = true
|
||||
}
|
||||
|
||||
}
|
||||
96
Pods/ZLPhotoBrowser/Sources/General/ZLAlbumListModel.swift
generated
Normal file
96
Pods/ZLPhotoBrowser/Sources/General/ZLAlbumListModel.swift
generated
Normal file
@@ -0,0 +1,96 @@
|
||||
//
|
||||
// ZLAlbumListModel.swift
|
||||
// ZLPhotoBrowser
|
||||
//
|
||||
// Created by long on 2020/8/11.
|
||||
//
|
||||
// Copyright (c) 2020 Long Zhang <495181165@qq.com>
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
// THE SOFTWARE.
|
||||
|
||||
import UIKit
|
||||
import Photos
|
||||
|
||||
public class ZLAlbumListModel: NSObject {
|
||||
|
||||
public let title: String
|
||||
|
||||
public var count: Int {
|
||||
return result.count
|
||||
}
|
||||
|
||||
public var result: PHFetchResult<PHAsset>
|
||||
|
||||
public let collection: PHAssetCollection
|
||||
|
||||
public let option: PHFetchOptions
|
||||
|
||||
public let isCameraRoll: Bool
|
||||
|
||||
public var headImageAsset: PHAsset? {
|
||||
return result.lastObject
|
||||
}
|
||||
|
||||
public var models: [ZLPhotoModel] = []
|
||||
|
||||
// 暂未用到
|
||||
private var selectedModels: [ZLPhotoModel] = []
|
||||
|
||||
// 暂未用到
|
||||
private var selectedCount: Int = 0
|
||||
|
||||
public init(
|
||||
title: String,
|
||||
result: PHFetchResult<PHAsset>,
|
||||
collection: PHAssetCollection,
|
||||
option: PHFetchOptions,
|
||||
isCameraRoll: Bool
|
||||
) {
|
||||
self.title = title
|
||||
self.result = result
|
||||
self.collection = collection
|
||||
self.option = option
|
||||
self.isCameraRoll = isCameraRoll
|
||||
}
|
||||
|
||||
public func refetchPhotos() {
|
||||
let models = ZLPhotoManager.fetchPhoto(
|
||||
in: result,
|
||||
ascending: ZLPhotoConfiguration.default().sortAscending,
|
||||
allowSelectImage: ZLPhotoConfiguration.default().allowSelectImage,
|
||||
allowSelectVideo: ZLPhotoConfiguration.default().allowSelectVideo
|
||||
)
|
||||
self.models.removeAll()
|
||||
self.models.append(contentsOf: models)
|
||||
}
|
||||
|
||||
func refreshResult() {
|
||||
result = PHAsset.fetchAssets(in: collection, options: option)
|
||||
}
|
||||
}
|
||||
|
||||
extension ZLAlbumListModel {
|
||||
|
||||
static func ==(lhs: ZLAlbumListModel, rhs: ZLAlbumListModel) -> Bool {
|
||||
return lhs.title == rhs.title &&
|
||||
lhs.count == rhs.count &&
|
||||
lhs.headImageAsset?.localIdentifier == rhs.headImageAsset?.localIdentifier
|
||||
}
|
||||
|
||||
}
|
||||
65
Pods/ZLPhotoBrowser/Sources/General/ZLAnimationUtils.swift
generated
Normal file
65
Pods/ZLPhotoBrowser/Sources/General/ZLAnimationUtils.swift
generated
Normal file
@@ -0,0 +1,65 @@
|
||||
//
|
||||
// ZLAnimationUtils.swift
|
||||
// ZLPhotoBrowser
|
||||
//
|
||||
// Created by long on 2023/1/13.
|
||||
//
|
||||
// Copyright (c) 2020 Long Zhang <495181165@qq.com>
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
// THE SOFTWARE.
|
||||
|
||||
import UIKit
|
||||
|
||||
class ZLAnimationUtils: NSObject {
|
||||
enum AnimationType: String {
|
||||
case fade = "opacity"
|
||||
case scale = "transform.scale"
|
||||
case rotate = "transform.rotation"
|
||||
}
|
||||
|
||||
class func animation(
|
||||
type: ZLAnimationUtils.AnimationType,
|
||||
fromValue: CGFloat,
|
||||
toValue: CGFloat,
|
||||
duration: TimeInterval
|
||||
) -> CAAnimation {
|
||||
let animation = CABasicAnimation(keyPath: type.rawValue)
|
||||
animation.fromValue = fromValue
|
||||
animation.toValue = toValue
|
||||
animation.duration = duration
|
||||
animation.fillMode = .forwards
|
||||
animation.isRemovedOnCompletion = false
|
||||
return animation
|
||||
}
|
||||
|
||||
class func springAnimation() -> CAKeyframeAnimation {
|
||||
let animate = CAKeyframeAnimation(keyPath: "transform")
|
||||
animate.duration = ZLPhotoConfiguration.default().selectBtnAnimationDuration
|
||||
animate.isRemovedOnCompletion = true
|
||||
animate.fillMode = .forwards
|
||||
|
||||
animate.values = [
|
||||
CATransform3DMakeScale(0.7, 0.7, 1),
|
||||
CATransform3DMakeScale(1.2, 1.2, 1),
|
||||
CATransform3DMakeScale(0.8, 0.8, 1),
|
||||
CATransform3DMakeScale(1, 1, 1),
|
||||
]
|
||||
return animate
|
||||
}
|
||||
}
|
||||
157
Pods/ZLPhotoBrowser/Sources/General/ZLCameraCell.swift
generated
Normal file
157
Pods/ZLPhotoBrowser/Sources/General/ZLCameraCell.swift
generated
Normal file
@@ -0,0 +1,157 @@
|
||||
//
|
||||
// ZLCameraCell.swift
|
||||
// ZLPhotoBrowser
|
||||
//
|
||||
// Created by long on 2020/8/19.
|
||||
//
|
||||
// Copyright (c) 2020 Long Zhang <495181165@qq.com>
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
// THE SOFTWARE.
|
||||
|
||||
import UIKit
|
||||
import AVFoundation
|
||||
|
||||
class ZLCameraCell: UICollectionViewCell {
|
||||
|
||||
private lazy var imageView: UIImageView = {
|
||||
let view = UIImageView(image: .zl.getImage("zl_takePhoto"))
|
||||
view.contentMode = .scaleAspectFit
|
||||
view.clipsToBounds = true
|
||||
return view
|
||||
}()
|
||||
|
||||
private var session: AVCaptureSession?
|
||||
|
||||
private var videoInput: AVCaptureDeviceInput?
|
||||
|
||||
private var photoOutput: AVCapturePhotoOutput?
|
||||
|
||||
private var previewLayer: AVCaptureVideoPreviewLayer?
|
||||
|
||||
var isEnable: Bool = true {
|
||||
didSet {
|
||||
contentView.alpha = isEnable ? 1 : 0.3
|
||||
}
|
||||
}
|
||||
|
||||
deinit {
|
||||
session?.stopRunning()
|
||||
session = nil
|
||||
zl_debugPrint("ZLCameraCell deinit")
|
||||
}
|
||||
|
||||
override init(frame: CGRect) {
|
||||
super.init(frame: frame)
|
||||
setupUI()
|
||||
}
|
||||
|
||||
@available(*, unavailable)
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
override func layoutSubviews() {
|
||||
super.layoutSubviews()
|
||||
|
||||
imageView.center = CGPoint(x: bounds.midX, y: bounds.midY)
|
||||
|
||||
previewLayer?.frame = contentView.layer.bounds
|
||||
}
|
||||
|
||||
private func setupUI() {
|
||||
layer.masksToBounds = true
|
||||
layer.cornerRadius = ZLPhotoUIConfiguration.default().cellCornerRadio
|
||||
|
||||
contentView.addSubview(imageView)
|
||||
backgroundColor = .zl.cameraCellBgColor
|
||||
}
|
||||
|
||||
private func setupSession() {
|
||||
guard session == nil, (session?.isRunning ?? false) == false else {
|
||||
return
|
||||
}
|
||||
session?.stopRunning()
|
||||
if let input = videoInput {
|
||||
session?.removeInput(input)
|
||||
}
|
||||
if let output = photoOutput {
|
||||
session?.removeOutput(output)
|
||||
}
|
||||
session = nil
|
||||
previewLayer?.removeFromSuperlayer()
|
||||
previewLayer = nil
|
||||
|
||||
guard let camera = backCamera() else {
|
||||
return
|
||||
}
|
||||
guard let input = try? AVCaptureDeviceInput(device: camera) else {
|
||||
return
|
||||
}
|
||||
videoInput = input
|
||||
photoOutput = AVCapturePhotoOutput()
|
||||
|
||||
session = AVCaptureSession()
|
||||
|
||||
if session?.canAddInput(input) == true {
|
||||
session?.addInput(input)
|
||||
}
|
||||
if session?.canAddOutput(photoOutput!) == true {
|
||||
session?.addOutput(photoOutput!)
|
||||
}
|
||||
|
||||
previewLayer = AVCaptureVideoPreviewLayer(session: session!)
|
||||
contentView.layer.masksToBounds = true
|
||||
previewLayer?.frame = contentView.layer.bounds
|
||||
previewLayer?.videoGravity = .resizeAspectFill
|
||||
contentView.layer.insertSublayer(previewLayer!, at: 0)
|
||||
|
||||
session?.startRunning()
|
||||
}
|
||||
|
||||
private func backCamera() -> AVCaptureDevice? {
|
||||
let devices = AVCaptureDevice.DiscoverySession(deviceTypes: [.builtInWideAngleCamera], mediaType: .video, position: .back).devices
|
||||
for device in devices {
|
||||
if device.position == .back {
|
||||
return device
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func startCapture() {
|
||||
let status = AVCaptureDevice.authorizationStatus(for: .video)
|
||||
|
||||
if !UIImagePickerController.isSourceTypeAvailable(.camera) || status == .denied {
|
||||
return
|
||||
}
|
||||
|
||||
if status == .notDetermined {
|
||||
AVCaptureDevice.requestAccess(for: .video) { granted in
|
||||
if granted {
|
||||
ZLMainAsync {
|
||||
self.setupSession()
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
setupSession()
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
286
Pods/ZLPhotoBrowser/Sources/General/ZLCameraConfiguration.swift
generated
Normal file
286
Pods/ZLPhotoBrowser/Sources/General/ZLCameraConfiguration.swift
generated
Normal file
@@ -0,0 +1,286 @@
|
||||
//
|
||||
// ZLCameraConfiguration.swift
|
||||
// ZLPhotoBrowser
|
||||
//
|
||||
// Created by long on 2021/11/10.
|
||||
//
|
||||
// Copyright (c) 2020 Long Zhang <495181165@qq.com>
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
// THE SOFTWARE.
|
||||
|
||||
import UIKit
|
||||
import AVFoundation
|
||||
|
||||
@objcMembers
|
||||
public class ZLCameraConfiguration: NSObject {
|
||||
private var pri_allowTakePhoto = true
|
||||
/// Allow taking photos in the camera (Need allowSelectImage to be true). Defaults to true.
|
||||
public var allowTakePhoto: Bool {
|
||||
get {
|
||||
pri_allowTakePhoto && ZLPhotoConfiguration.default().allowSelectImage
|
||||
}
|
||||
set {
|
||||
pri_allowTakePhoto = newValue
|
||||
}
|
||||
}
|
||||
|
||||
private var pri_allowRecordVideo = true
|
||||
/// Allow recording in the camera (Need allowSelectVideo to be true). Defaults to true.
|
||||
public var allowRecordVideo: Bool {
|
||||
get {
|
||||
pri_allowRecordVideo && ZLPhotoConfiguration.default().allowSelectVideo
|
||||
}
|
||||
set {
|
||||
pri_allowRecordVideo = newValue
|
||||
}
|
||||
}
|
||||
|
||||
private var pri_minRecordDuration: ZLPhotoConfiguration.Second = 0
|
||||
/// Minimum recording duration. Defaults to 0.
|
||||
public var minRecordDuration: ZLPhotoConfiguration.Second {
|
||||
get {
|
||||
pri_minRecordDuration
|
||||
}
|
||||
set {
|
||||
pri_minRecordDuration = max(0, newValue)
|
||||
}
|
||||
}
|
||||
|
||||
private var pri_maxRecordDuration: ZLPhotoConfiguration.Second = 20
|
||||
/// Maximum recording duration. Defaults to 20, minimum is 1.
|
||||
public var maxRecordDuration: ZLPhotoConfiguration.Second {
|
||||
get {
|
||||
pri_maxRecordDuration
|
||||
}
|
||||
set {
|
||||
pri_maxRecordDuration = max(1, newValue)
|
||||
}
|
||||
}
|
||||
|
||||
/// Video resolution. Defaults to hd1920x1080.
|
||||
public var sessionPreset: ZLCameraConfiguration.CaptureSessionPreset = .hd1920x1080
|
||||
|
||||
/// Camera focus mode. Defaults to continuousAutoFocus
|
||||
public var focusMode: ZLCameraConfiguration.FocusMode = .continuousAutoFocus
|
||||
|
||||
/// Camera exposure mode. Defaults to continuousAutoExposure
|
||||
public var exposureMode: ZLCameraConfiguration.ExposureMode = .continuousAutoExposure
|
||||
|
||||
/// Camera flahs switch. Defaults to true.
|
||||
public var showFlashSwitch = true
|
||||
|
||||
/// Whether to support switch camera. Defaults to true.
|
||||
public var allowSwitchCamera = true
|
||||
|
||||
/// Video export format for recording video and editing video. Defaults to mov.
|
||||
public var videoExportType: ZLCameraConfiguration.VideoExportType = .mov
|
||||
|
||||
/// The default camera position after entering the camera. Defaults to back.
|
||||
public var devicePosition: ZLCameraConfiguration.DevicePosition = .back
|
||||
|
||||
private var pri_videoCodecType: Any?
|
||||
/// The codecs for video capture. Defaults to .h264
|
||||
@available(iOS 11.0, *)
|
||||
public var videoCodecType: AVVideoCodecType {
|
||||
get {
|
||||
(pri_videoCodecType as? AVVideoCodecType) ?? .h264
|
||||
}
|
||||
set {
|
||||
pri_videoCodecType = newValue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public extension ZLCameraConfiguration {
|
||||
@objc enum CaptureSessionPreset: Int {
|
||||
var avSessionPreset: AVCaptureSession.Preset {
|
||||
switch self {
|
||||
case .cif352x288:
|
||||
return .cif352x288
|
||||
case .vga640x480:
|
||||
return .vga640x480
|
||||
case .hd1280x720:
|
||||
return .hd1280x720
|
||||
case .hd1920x1080:
|
||||
return .hd1920x1080
|
||||
case .photo:
|
||||
return .photo
|
||||
}
|
||||
}
|
||||
|
||||
case cif352x288
|
||||
case vga640x480
|
||||
case hd1280x720
|
||||
case hd1920x1080
|
||||
case photo
|
||||
}
|
||||
|
||||
@objc enum FocusMode: Int {
|
||||
var avFocusMode: AVCaptureDevice.FocusMode {
|
||||
switch self {
|
||||
case .autoFocus:
|
||||
return .autoFocus
|
||||
case .continuousAutoFocus:
|
||||
return .continuousAutoFocus
|
||||
}
|
||||
}
|
||||
|
||||
case autoFocus
|
||||
case continuousAutoFocus
|
||||
}
|
||||
|
||||
@objc enum ExposureMode: Int {
|
||||
var avFocusMode: AVCaptureDevice.ExposureMode {
|
||||
switch self {
|
||||
case .autoExpose:
|
||||
return .autoExpose
|
||||
case .continuousAutoExposure:
|
||||
return .continuousAutoExposure
|
||||
}
|
||||
}
|
||||
|
||||
case autoExpose
|
||||
case continuousAutoExposure
|
||||
}
|
||||
|
||||
@objc enum VideoExportType: Int {
|
||||
var format: String {
|
||||
switch self {
|
||||
case .mov:
|
||||
return "mov"
|
||||
case .mp4:
|
||||
return "mp4"
|
||||
}
|
||||
}
|
||||
|
||||
var avFileType: AVFileType {
|
||||
switch self {
|
||||
case .mov:
|
||||
return .mov
|
||||
case .mp4:
|
||||
return .mp4
|
||||
}
|
||||
}
|
||||
|
||||
case mov
|
||||
case mp4
|
||||
}
|
||||
|
||||
@objc enum DevicePosition: Int {
|
||||
case back
|
||||
case front
|
||||
|
||||
/// For custom camera
|
||||
var avDevicePosition: AVCaptureDevice.Position {
|
||||
switch self {
|
||||
case .back:
|
||||
return .back
|
||||
case .front:
|
||||
return .front
|
||||
}
|
||||
}
|
||||
|
||||
/// For system camera
|
||||
var cameraDevice: UIImagePickerController.CameraDevice {
|
||||
switch self {
|
||||
case .back:
|
||||
return .rear
|
||||
case .front:
|
||||
return .front
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: chaining
|
||||
|
||||
public extension ZLCameraConfiguration {
|
||||
@discardableResult
|
||||
func allowTakePhoto(_ value: Bool) -> ZLCameraConfiguration {
|
||||
allowTakePhoto = value
|
||||
return self
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
func allowRecordVideo(_ value: Bool) -> ZLCameraConfiguration {
|
||||
allowRecordVideo = value
|
||||
return self
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
func minRecordDuration(_ duration: ZLPhotoConfiguration.Second) -> ZLCameraConfiguration {
|
||||
minRecordDuration = duration
|
||||
return self
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
func maxRecordDuration(_ duration: ZLPhotoConfiguration.Second) -> ZLCameraConfiguration {
|
||||
maxRecordDuration = duration
|
||||
return self
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
func sessionPreset(_ sessionPreset: ZLCameraConfiguration.CaptureSessionPreset) -> ZLCameraConfiguration {
|
||||
self.sessionPreset = sessionPreset
|
||||
return self
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
func focusMode(_ mode: ZLCameraConfiguration.FocusMode) -> ZLCameraConfiguration {
|
||||
focusMode = mode
|
||||
return self
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
func exposureMode(_ mode: ZLCameraConfiguration.ExposureMode) -> ZLCameraConfiguration {
|
||||
exposureMode = mode
|
||||
return self
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
func showFlashSwitch(_ value: Bool) -> ZLCameraConfiguration {
|
||||
showFlashSwitch = value
|
||||
return self
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
func allowSwitchCamera(_ value: Bool) -> ZLCameraConfiguration {
|
||||
allowSwitchCamera = value
|
||||
return self
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
func videoExportType(_ type: ZLCameraConfiguration.VideoExportType) -> ZLCameraConfiguration {
|
||||
videoExportType = type
|
||||
return self
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
func devicePosition(_ position: ZLCameraConfiguration.DevicePosition) -> ZLCameraConfiguration {
|
||||
devicePosition = position
|
||||
return self
|
||||
}
|
||||
|
||||
@available(iOS 11.0, *)
|
||||
@discardableResult
|
||||
func videoCodecType(_ type: AVVideoCodecType) -> ZLCameraConfiguration {
|
||||
videoCodecType = type
|
||||
return self
|
||||
}
|
||||
}
|
||||
31
Pods/ZLPhotoBrowser/Sources/General/ZLCollectionViewFlowLayout.swift
generated
Normal file
31
Pods/ZLPhotoBrowser/Sources/General/ZLCollectionViewFlowLayout.swift
generated
Normal file
@@ -0,0 +1,31 @@
|
||||
//
|
||||
// ZLCollectionViewFlowLayout.swift
|
||||
// ZLPhotoBrowser
|
||||
//
|
||||
// Created by long on 2023/4/20.
|
||||
//
|
||||
// Copyright (c) 2020 Long Zhang <495181165@qq.com>
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
// THE SOFTWARE.
|
||||
|
||||
import UIKit
|
||||
|
||||
class ZLCollectionViewFlowLayout: UICollectionViewFlowLayout {
|
||||
override var flipsHorizontallyInOppositeLayoutDirection: Bool { isRTL() }
|
||||
}
|
||||
83
Pods/ZLPhotoBrowser/Sources/General/ZLCustomAlertProtocol.swift
generated
Normal file
83
Pods/ZLPhotoBrowser/Sources/General/ZLCustomAlertProtocol.swift
generated
Normal file
@@ -0,0 +1,83 @@
|
||||
//
|
||||
// ZLCustomAlertProtocol.swift
|
||||
// ZLPhotoBrowser
|
||||
//
|
||||
// Created by long on 2022/6/29.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
public enum ZLCustomAlertStyle {
|
||||
case alert
|
||||
case actionSheet
|
||||
}
|
||||
|
||||
public protocol ZLCustomAlertProtocol: AnyObject {
|
||||
/// Should return an instance of ZLCustomAlertProtocol
|
||||
static func alert(title: String?, message: String, style: ZLCustomAlertStyle) -> ZLCustomAlertProtocol
|
||||
|
||||
func addAction(_ action: ZLCustomAlertAction)
|
||||
|
||||
func show(with parentVC: UIViewController?)
|
||||
}
|
||||
|
||||
public class ZLCustomAlertAction: NSObject {
|
||||
public enum Style {
|
||||
case `default`
|
||||
case tint
|
||||
case cancel
|
||||
case destructive
|
||||
}
|
||||
|
||||
public let title: String
|
||||
|
||||
public let style: ZLCustomAlertAction.Style
|
||||
|
||||
public let handler: ((ZLCustomAlertAction) -> Void)?
|
||||
|
||||
deinit {
|
||||
zl_debugPrint("ZLCustomAlertAction deinit")
|
||||
}
|
||||
|
||||
public init(title: String, style: ZLCustomAlertAction.Style, handler: ((ZLCustomAlertAction) -> Void)?) {
|
||||
self.title = title
|
||||
self.style = style
|
||||
self.handler = handler
|
||||
super.init()
|
||||
}
|
||||
}
|
||||
|
||||
/// internal
|
||||
extension ZLCustomAlertStyle {
|
||||
var toSystemAlertStyle: UIAlertController.Style {
|
||||
switch self {
|
||||
case .alert:
|
||||
return .alert
|
||||
case .actionSheet:
|
||||
return .actionSheet
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// internal
|
||||
extension ZLCustomAlertAction.Style {
|
||||
var toSystemAlertActionStyle: UIAlertAction.Style {
|
||||
switch self {
|
||||
case .default, .tint:
|
||||
return .default
|
||||
case .cancel:
|
||||
return .cancel
|
||||
case .destructive:
|
||||
return .destructive
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// internal
|
||||
extension ZLCustomAlertAction {
|
||||
func toSystemAlertAction() -> UIAlertAction {
|
||||
return UIAlertAction(title: title, style: style.toSystemAlertActionStyle) { _ in
|
||||
self.handler?(self)
|
||||
}
|
||||
}
|
||||
}
|
||||
360
Pods/ZLPhotoBrowser/Sources/General/ZLEditImageConfiguration.swift
generated
Normal file
360
Pods/ZLPhotoBrowser/Sources/General/ZLEditImageConfiguration.swift
generated
Normal file
@@ -0,0 +1,360 @@
|
||||
//
|
||||
// ZLEditImageConfiguration.swift
|
||||
// ZLPhotoBrowser
|
||||
//
|
||||
// Created by long on 2021/12/17.
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
// THE SOFTWARE.
|
||||
|
||||
import UIKit
|
||||
|
||||
/// Provide an image sticker container view that conform to this protocol must be a subclass of UIView
|
||||
/// 必须是UIView的子类遵循这个协议
|
||||
@objc public protocol ZLImageStickerContainerDelegate {
|
||||
@objc var selectImageBlock: ((UIImage) -> Void)? { get set }
|
||||
|
||||
@objc var hideBlock: (() -> Void)? { get set }
|
||||
|
||||
@objc func show(in view: UIView)
|
||||
}
|
||||
|
||||
@objcMembers
|
||||
public class ZLEditImageConfiguration: NSObject {
|
||||
private var pri_tools: [ZLEditImageConfiguration.EditTool] = ZLEditImageConfiguration.EditTool.allCases
|
||||
/// Edit image tools. (Default order is draw, clip, imageSticker, textSticker, mosaic, filtter)
|
||||
/// Because Objective-C Array can't contain Enum styles, so this property is invalid in Objective-C.
|
||||
/// - warning: If you want to use the image sticker feature, you must provide a view that implements ZLImageStickerContainerDelegate.
|
||||
public var tools: [ZLEditImageConfiguration.EditTool] {
|
||||
get {
|
||||
if pri_tools.isEmpty {
|
||||
return ZLEditImageConfiguration.EditTool.allCases
|
||||
} else {
|
||||
return pri_tools
|
||||
}
|
||||
}
|
||||
set {
|
||||
pri_tools = newValue
|
||||
}
|
||||
}
|
||||
|
||||
/// Edit image tools. (This property is only for objc).
|
||||
/// - warning: If you want to use the image sticker feature, you must provide a view that implements ZLImageStickerContainerDelegate.
|
||||
public var tools_objc: [Int] = [] {
|
||||
didSet {
|
||||
tools = tools_objc.compactMap { ZLEditImageConfiguration.EditTool(rawValue: $0) }
|
||||
}
|
||||
}
|
||||
|
||||
private static let defaultDrawColors: [UIColor] = [
|
||||
.white,
|
||||
.black,
|
||||
.zl.rgba(249, 80, 81),
|
||||
.zl.rgba(248, 156, 59),
|
||||
.zl.rgba(255, 195, 0),
|
||||
.zl.rgba(145, 211, 0),
|
||||
.zl.rgba(0, 193, 94),
|
||||
.zl.rgba(16, 173, 254),
|
||||
.zl.rgba(16, 132, 236),
|
||||
.zl.rgba(99, 103, 240),
|
||||
.zl.rgba(127, 127, 127)
|
||||
]
|
||||
|
||||
private var pri_drawColors = ZLEditImageConfiguration.defaultDrawColors
|
||||
/// Draw colors for image editor.
|
||||
public var drawColors: [UIColor] {
|
||||
get {
|
||||
if pri_drawColors.isEmpty {
|
||||
return ZLEditImageConfiguration.defaultDrawColors
|
||||
} else {
|
||||
return pri_drawColors
|
||||
}
|
||||
}
|
||||
set {
|
||||
pri_drawColors = newValue
|
||||
}
|
||||
}
|
||||
|
||||
/// The default draw color. If this color not in editImageDrawColors, will pick the first color in editImageDrawColors as the default.
|
||||
public var defaultDrawColor: UIColor = .zl.rgba(249, 80, 81)
|
||||
|
||||
private var pri_clipRatios: [ZLImageClipRatio] = [.custom]
|
||||
/// Edit ratios for image editor.
|
||||
public var clipRatios: [ZLImageClipRatio] {
|
||||
get {
|
||||
if pri_clipRatios.isEmpty {
|
||||
return [.custom]
|
||||
} else {
|
||||
return pri_clipRatios
|
||||
}
|
||||
}
|
||||
set {
|
||||
pri_clipRatios = newValue
|
||||
}
|
||||
}
|
||||
|
||||
private static let defaultTextStickerTextColors: [UIColor] = [
|
||||
.white,
|
||||
.black,
|
||||
.zl.rgba(249, 80, 81),
|
||||
.zl.rgba(248, 156, 59),
|
||||
.zl.rgba(255, 195, 0),
|
||||
.zl.rgba(145, 211, 0),
|
||||
.zl.rgba(0, 193, 94),
|
||||
.zl.rgba(16, 173, 254),
|
||||
.zl.rgba(16, 132, 236),
|
||||
.zl.rgba(99, 103, 240),
|
||||
.zl.rgba(127, 127, 127)
|
||||
]
|
||||
|
||||
private var pri_textStickerTextColors: [UIColor] = ZLEditImageConfiguration.defaultTextStickerTextColors
|
||||
/// Text sticker colors for image editor.
|
||||
public var textStickerTextColors: [UIColor] {
|
||||
get {
|
||||
if pri_textStickerTextColors.isEmpty {
|
||||
return ZLEditImageConfiguration.defaultTextStickerTextColors
|
||||
} else {
|
||||
return pri_textStickerTextColors
|
||||
}
|
||||
}
|
||||
set {
|
||||
pri_textStickerTextColors = newValue
|
||||
}
|
||||
}
|
||||
|
||||
/// The default text sticker color. If this color not in textStickerTextColors, will pick the first color in textStickerTextColors as the default.
|
||||
public var textStickerDefaultTextColor = UIColor.white
|
||||
|
||||
private var pri_filters: [ZLFilter] = ZLFilter.all
|
||||
/// Filters for image editor.
|
||||
public var filters: [ZLFilter] {
|
||||
get {
|
||||
if pri_filters.isEmpty {
|
||||
return ZLFilter.all
|
||||
} else {
|
||||
return pri_filters
|
||||
}
|
||||
}
|
||||
set {
|
||||
pri_filters = newValue
|
||||
}
|
||||
}
|
||||
|
||||
public var imageStickerContainerView: (UIView & ZLImageStickerContainerDelegate)?
|
||||
|
||||
private var pri_adjustTools: [ZLEditImageConfiguration.AdjustTool] = ZLEditImageConfiguration.AdjustTool.allCases
|
||||
/// Adjust image tools. (Default order is brightness, contrast, saturation)
|
||||
/// Valid when the tools contain EditTool.adjust
|
||||
/// Because Objective-C Array can't contain Enum styles, so this property is invalid in Objective-C.
|
||||
public var adjustTools: [ZLEditImageConfiguration.AdjustTool] {
|
||||
get {
|
||||
if pri_adjustTools.isEmpty {
|
||||
return ZLEditImageConfiguration.AdjustTool.allCases
|
||||
} else {
|
||||
return pri_adjustTools
|
||||
}
|
||||
}
|
||||
set {
|
||||
pri_adjustTools = newValue
|
||||
}
|
||||
}
|
||||
|
||||
/// Adjust image tools. (This property is only for objc).
|
||||
/// Valid when the tools contain EditTool.adjust
|
||||
public var adjustTools_objc: [Int] = [] {
|
||||
didSet {
|
||||
adjustTools = adjustTools_objc.compactMap { ZLEditImageConfiguration.AdjustTool(rawValue: $0) }
|
||||
}
|
||||
}
|
||||
|
||||
/// Give an impact feedback when the adjust slider value is zero. Defaults to true.
|
||||
public var impactFeedbackWhenAdjustSliderValueIsZero = true
|
||||
|
||||
/// Impact feedback style. Defaults to .medium
|
||||
public var impactFeedbackStyle: UIImpactFeedbackGenerator.FeedbackStyle = .medium
|
||||
|
||||
/// Whether to support redo in graffiti and mosaic tools. Defaults to false
|
||||
public var canRedo = false
|
||||
}
|
||||
|
||||
public extension ZLEditImageConfiguration {
|
||||
@objc enum EditTool: Int, CaseIterable {
|
||||
case draw
|
||||
case clip
|
||||
case imageSticker
|
||||
case textSticker
|
||||
case mosaic
|
||||
case filter
|
||||
case adjust
|
||||
}
|
||||
|
||||
@objc enum AdjustTool: Int, CaseIterable {
|
||||
case brightness
|
||||
case contrast
|
||||
case saturation
|
||||
|
||||
var key: String {
|
||||
switch self {
|
||||
case .brightness:
|
||||
return kCIInputBrightnessKey
|
||||
case .contrast:
|
||||
return kCIInputContrastKey
|
||||
case .saturation:
|
||||
return kCIInputSaturationKey
|
||||
}
|
||||
}
|
||||
|
||||
func filterValue(_ value: Float) -> Float {
|
||||
switch self {
|
||||
case .brightness:
|
||||
// 亮度范围-1---1,默认0,这里除以3,取 -0.33---0.33
|
||||
return value / 3
|
||||
case .contrast:
|
||||
// 对比度范围0---4,默认1,这里计算下取0.5---2.5
|
||||
let v: Float
|
||||
if value < 0 {
|
||||
v = 1 + value * (1 / 2)
|
||||
} else {
|
||||
v = 1 + value * (3 / 2)
|
||||
}
|
||||
return v
|
||||
case .saturation:
|
||||
// 饱和度范围0---2,默认1
|
||||
return value + 1
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: chaining
|
||||
|
||||
public extension ZLEditImageConfiguration {
|
||||
@discardableResult
|
||||
func tools(_ tools: [ZLEditImageConfiguration.EditTool]) -> ZLEditImageConfiguration {
|
||||
self.tools = tools
|
||||
return self
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
func drawColors(_ colors: [UIColor]) -> ZLEditImageConfiguration {
|
||||
drawColors = colors
|
||||
return self
|
||||
}
|
||||
|
||||
func defaultDrawColor(_ color: UIColor) -> ZLEditImageConfiguration {
|
||||
defaultDrawColor = color
|
||||
return self
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
func clipRatios(_ ratios: [ZLImageClipRatio]) -> ZLEditImageConfiguration {
|
||||
clipRatios = ratios
|
||||
return self
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
func textStickerTextColors(_ colors: [UIColor]) -> ZLEditImageConfiguration {
|
||||
textStickerTextColors = colors
|
||||
return self
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
func textStickerDefaultTextColor(_ color: UIColor) -> ZLEditImageConfiguration {
|
||||
textStickerDefaultTextColor = color
|
||||
return self
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
func filters(_ filters: [ZLFilter]) -> ZLEditImageConfiguration {
|
||||
self.filters = filters
|
||||
return self
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
func imageStickerContainerView(_ view: (UIView & ZLImageStickerContainerDelegate)?) -> ZLEditImageConfiguration {
|
||||
imageStickerContainerView = view
|
||||
return self
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
func adjustTools(_ tools: [ZLEditImageConfiguration.AdjustTool]) -> ZLEditImageConfiguration {
|
||||
adjustTools = tools
|
||||
return self
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
func impactFeedbackWhenAdjustSliderValueIsZero(_ value: Bool) -> ZLEditImageConfiguration {
|
||||
impactFeedbackWhenAdjustSliderValueIsZero = value
|
||||
return self
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
func impactFeedbackStyle(_ style: UIImpactFeedbackGenerator.FeedbackStyle) -> ZLEditImageConfiguration {
|
||||
impactFeedbackStyle = style
|
||||
return self
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
func canRedo(_ value: Bool) -> ZLEditImageConfiguration {
|
||||
canRedo = value
|
||||
return self
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: 裁剪比例
|
||||
|
||||
public class ZLImageClipRatio: NSObject {
|
||||
@objc public var title: String
|
||||
|
||||
@objc public let whRatio: CGFloat
|
||||
|
||||
@objc public let isCircle: Bool
|
||||
|
||||
@objc public init(title: String, whRatio: CGFloat, isCircle: Bool = false) {
|
||||
self.title = title
|
||||
self.whRatio = isCircle ? 1 : whRatio
|
||||
self.isCircle = isCircle
|
||||
super.init()
|
||||
}
|
||||
}
|
||||
|
||||
extension ZLImageClipRatio {
|
||||
static func == (lhs: ZLImageClipRatio, rhs: ZLImageClipRatio) -> Bool {
|
||||
return lhs.whRatio == rhs.whRatio && lhs.title == rhs.title
|
||||
}
|
||||
}
|
||||
|
||||
public extension ZLImageClipRatio {
|
||||
@objc static let custom = ZLImageClipRatio(title: "custom", whRatio: 0)
|
||||
|
||||
@objc static let circle = ZLImageClipRatio(title: "circle", whRatio: 1, isCircle: true)
|
||||
|
||||
@objc static let wh1x1 = ZLImageClipRatio(title: "1 : 1", whRatio: 1)
|
||||
|
||||
@objc static let wh3x4 = ZLImageClipRatio(title: "3 : 4", whRatio: 3.0 / 4.0)
|
||||
|
||||
@objc static let wh4x3 = ZLImageClipRatio(title: "4 : 3", whRatio: 4.0 / 3.0)
|
||||
|
||||
@objc static let wh2x3 = ZLImageClipRatio(title: "2 : 3", whRatio: 2.0 / 3.0)
|
||||
|
||||
@objc static let wh3x2 = ZLImageClipRatio(title: "3 : 2", whRatio: 3.0 / 2.0)
|
||||
|
||||
@objc static let wh9x16 = ZLImageClipRatio(title: "9 : 16", whRatio: 9.0 / 16.0)
|
||||
|
||||
@objc static let wh16x9 = ZLImageClipRatio(title: "16 : 9", whRatio: 16.0 / 9.0)
|
||||
}
|
||||
232
Pods/ZLPhotoBrowser/Sources/General/ZLEmbedAlbumListView.swift
generated
Normal file
232
Pods/ZLPhotoBrowser/Sources/General/ZLEmbedAlbumListView.swift
generated
Normal file
@@ -0,0 +1,232 @@
|
||||
//
|
||||
// ZLEmbedAlbumListView.swift
|
||||
// ZLPhotoBrowser
|
||||
//
|
||||
// Created by long on 2020/9/7.
|
||||
//
|
||||
// Copyright (c) 2020 Long Zhang <495181165@qq.com>
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
// THE SOFTWARE.
|
||||
|
||||
import UIKit
|
||||
import Photos
|
||||
|
||||
class ZLEmbedAlbumListView: UIView {
|
||||
static let rowH: CGFloat = 60
|
||||
|
||||
private var selectedAlbum: ZLAlbumListModel
|
||||
|
||||
private lazy var tableBgView = UIView()
|
||||
|
||||
private lazy var tableView: UITableView = {
|
||||
let view = UITableView(frame: .zero, style: .plain)
|
||||
view.backgroundColor = .zl.albumListBgColor
|
||||
view.tableFooterView = UIView()
|
||||
view.rowHeight = ZLEmbedAlbumListView.rowH
|
||||
view.separatorInset = UIEdgeInsets(top: 0, left: 12, bottom: 0, right: 0)
|
||||
view.separatorColor = .zl.separatorLineColor
|
||||
view.delegate = self
|
||||
view.dataSource = self
|
||||
ZLAlbumListCell.zl.register(view)
|
||||
return view
|
||||
}()
|
||||
|
||||
private var arrDataSource: [ZLAlbumListModel] = []
|
||||
|
||||
var selectAlbumBlock: ((ZLAlbumListModel) -> Void)?
|
||||
|
||||
var hideBlock: (() -> Void)?
|
||||
|
||||
private var orientation: UIInterfaceOrientation = UIApplication.shared.statusBarOrientation
|
||||
|
||||
init(selectedAlbum: ZLAlbumListModel) {
|
||||
self.selectedAlbum = selectedAlbum
|
||||
super.init(frame: .zero)
|
||||
setupUI()
|
||||
loadAlbumList()
|
||||
}
|
||||
|
||||
@available(*, unavailable)
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
override func layoutSubviews() {
|
||||
super.layoutSubviews()
|
||||
let currOri = UIApplication.shared.statusBarOrientation
|
||||
|
||||
guard currOri != orientation else {
|
||||
return
|
||||
}
|
||||
orientation = currOri
|
||||
|
||||
guard !isHidden else {
|
||||
return
|
||||
}
|
||||
|
||||
let bgFrame = calculateBgViewBounds()
|
||||
|
||||
let path = UIBezierPath(roundedRect: CGRect(x: 0, y: 0, width: frame.width, height: bgFrame.height), byRoundingCorners: [.bottomLeft, .bottomRight], cornerRadii: CGSize(width: 8, height: 8))
|
||||
tableBgView.layer.mask = nil
|
||||
let maskLayer = CAShapeLayer()
|
||||
maskLayer.path = path.cgPath
|
||||
tableBgView.layer.mask = maskLayer
|
||||
|
||||
tableBgView.frame = bgFrame
|
||||
tableView.frame = tableBgView.bounds
|
||||
}
|
||||
|
||||
private func setupUI() {
|
||||
clipsToBounds = true
|
||||
|
||||
backgroundColor = .zl.embedAlbumListTranslucentColor
|
||||
|
||||
addSubview(tableBgView)
|
||||
tableBgView.addSubview(tableView)
|
||||
|
||||
let tap = UITapGestureRecognizer(target: self, action: #selector(tapAction(_:)))
|
||||
tap.delegate = self
|
||||
addGestureRecognizer(tap)
|
||||
}
|
||||
|
||||
private func loadAlbumList(completion: (() -> Void)? = nil) {
|
||||
DispatchQueue.global().async {
|
||||
ZLPhotoManager.getPhotoAlbumList(ascending: ZLPhotoConfiguration.default().sortAscending, allowSelectImage: ZLPhotoConfiguration.default().allowSelectImage, allowSelectVideo: ZLPhotoConfiguration.default().allowSelectVideo) { [weak self] albumList in
|
||||
self?.arrDataSource.removeAll()
|
||||
self?.arrDataSource.append(contentsOf: albumList)
|
||||
|
||||
ZLMainAsync {
|
||||
completion?()
|
||||
self?.tableView.reloadData()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func calculateBgViewBounds() -> CGRect {
|
||||
let contentH = CGFloat(arrDataSource.count) * ZLEmbedAlbumListView.rowH
|
||||
|
||||
let maxH: CGFloat
|
||||
if UIApplication.shared.statusBarOrientation.isPortrait {
|
||||
maxH = min(frame.height * 0.7, contentH)
|
||||
} else {
|
||||
maxH = min(frame.height * 0.8, contentH)
|
||||
}
|
||||
|
||||
return CGRect(x: 0, y: 0, width: frame.width, height: maxH)
|
||||
}
|
||||
|
||||
@objc private func tapAction(_ tap: UITapGestureRecognizer) {
|
||||
hide()
|
||||
hideBlock?()
|
||||
}
|
||||
|
||||
/// 这里不采用监听相册发生变化的方式,是因为每次变化,系统都会回调多次,造成重复获取相册列表
|
||||
func show(reloadAlbumList: Bool) {
|
||||
guard reloadAlbumList else {
|
||||
animateShow()
|
||||
return
|
||||
}
|
||||
|
||||
if #available(iOS 14.0, *), PHPhotoLibrary.authorizationStatus(for: .readWrite) == .limited {
|
||||
loadAlbumList { [weak self] in
|
||||
self?.animateShow()
|
||||
}
|
||||
} else {
|
||||
loadAlbumList()
|
||||
animateShow()
|
||||
}
|
||||
}
|
||||
|
||||
func hide() {
|
||||
var toFrame = tableBgView.frame
|
||||
toFrame.origin.y = -toFrame.height
|
||||
|
||||
UIView.animate(withDuration: 0.25, animations: {
|
||||
self.alpha = 0
|
||||
self.tableBgView.frame = toFrame
|
||||
}) { _ in
|
||||
self.isHidden = true
|
||||
self.alpha = 1
|
||||
}
|
||||
}
|
||||
|
||||
private func animateShow() {
|
||||
let toFrame = calculateBgViewBounds()
|
||||
|
||||
isHidden = false
|
||||
alpha = 0
|
||||
var newFrame = toFrame
|
||||
newFrame.origin.y -= newFrame.height
|
||||
|
||||
if newFrame != tableBgView.frame {
|
||||
let path = UIBezierPath(
|
||||
roundedRect: CGRect(x: 0, y: 0, width: newFrame.width, height: newFrame.height),
|
||||
byRoundingCorners: [.bottomLeft, .bottomRight],
|
||||
cornerRadii: CGSize(width: 8, height: 8)
|
||||
)
|
||||
tableBgView.layer.mask = nil
|
||||
let maskLayer = CAShapeLayer()
|
||||
maskLayer.path = path.cgPath
|
||||
tableBgView.layer.mask = maskLayer
|
||||
}
|
||||
|
||||
tableBgView.frame = newFrame
|
||||
tableView.frame = tableBgView.bounds
|
||||
UIView.animate(withDuration: 0.25) {
|
||||
self.alpha = 1
|
||||
self.tableBgView.frame = toFrame
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension ZLEmbedAlbumListView: UIGestureRecognizerDelegate {
|
||||
override func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool {
|
||||
let point = gestureRecognizer.location(in: self)
|
||||
return !tableBgView.frame.contains(point)
|
||||
}
|
||||
}
|
||||
|
||||
extension ZLEmbedAlbumListView: UITableViewDataSource, UITableViewDelegate {
|
||||
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
|
||||
return arrDataSource.count
|
||||
}
|
||||
|
||||
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
|
||||
let cell = tableView.dequeueReusableCell(withIdentifier: ZLAlbumListCell.zl.identifier, for: indexPath) as! ZLAlbumListCell
|
||||
|
||||
let m = arrDataSource[indexPath.row]
|
||||
|
||||
cell.configureCell(model: m, style: .embedAlbumList)
|
||||
|
||||
cell.selectBtn.isSelected = m == selectedAlbum
|
||||
|
||||
return cell
|
||||
}
|
||||
|
||||
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
|
||||
let m = arrDataSource[indexPath.row]
|
||||
selectedAlbum = m
|
||||
selectAlbumBlock?(m)
|
||||
hide()
|
||||
if let indexPaths = tableView.indexPathsForVisibleRows {
|
||||
tableView.reloadRows(at: indexPaths, with: .none)
|
||||
}
|
||||
}
|
||||
}
|
||||
68
Pods/ZLPhotoBrowser/Sources/General/ZLEnlargeButton.swift
generated
Normal file
68
Pods/ZLPhotoBrowser/Sources/General/ZLEnlargeButton.swift
generated
Normal file
@@ -0,0 +1,68 @@
|
||||
//
|
||||
// ZLEnlargeButton.swift
|
||||
// ZLPhotoBrowser
|
||||
//
|
||||
// Created by long on 2022/4/24.
|
||||
//
|
||||
// Copyright (c) 2020 Long Zhang <495181165@qq.com>
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
// THE SOFTWARE.
|
||||
|
||||
import UIKit
|
||||
|
||||
public class ZLEnlargeButton: UIButton {
|
||||
|
||||
/// 扩大点击区域
|
||||
public var enlargeInsets: UIEdgeInsets = .zero
|
||||
|
||||
/// 上下左右均扩大该值的点击范围
|
||||
public var enlargeInset: CGFloat = 0 {
|
||||
didSet {
|
||||
let inset = max(0, enlargeInset)
|
||||
enlargeInsets = UIEdgeInsets(top: inset, left: inset, bottom: inset, right: inset)
|
||||
}
|
||||
}
|
||||
|
||||
override public func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
|
||||
guard !isHidden, alpha != 0 else {
|
||||
return false
|
||||
}
|
||||
|
||||
let rect = enlargeRect()
|
||||
if rect.equalTo(bounds) {
|
||||
return super.point(inside: point, with: event)
|
||||
}
|
||||
return rect.contains(point) ? true : false
|
||||
}
|
||||
|
||||
private func enlargeRect() -> CGRect {
|
||||
guard enlargeInsets != .zero else {
|
||||
return bounds
|
||||
}
|
||||
|
||||
let rect = CGRect(
|
||||
x: bounds.minX - enlargeInsets.left,
|
||||
y: bounds.minY - enlargeInsets.top,
|
||||
width: bounds.width + enlargeInsets.left + enlargeInsets.right,
|
||||
height: bounds.height + enlargeInsets.top + enlargeInsets.bottom
|
||||
)
|
||||
return rect
|
||||
}
|
||||
|
||||
}
|
||||
181
Pods/ZLPhotoBrowser/Sources/General/ZLFetchImageOperation.swift
generated
Normal file
181
Pods/ZLPhotoBrowser/Sources/General/ZLFetchImageOperation.swift
generated
Normal file
@@ -0,0 +1,181 @@
|
||||
//
|
||||
// ZLFetchImageOperation.swift
|
||||
// ZLPhotoBrowser
|
||||
//
|
||||
// Created by long on 2020/8/18.
|
||||
//
|
||||
// Copyright (c) 2020 Long Zhang <495181165@qq.com>
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
// THE SOFTWARE.
|
||||
|
||||
import UIKit
|
||||
import Photos
|
||||
|
||||
class ZLFetchImageOperation: Operation {
|
||||
private let model: ZLPhotoModel
|
||||
|
||||
private let isOriginal: Bool
|
||||
|
||||
private let progress: ((CGFloat, Error?, UnsafeMutablePointer<ObjCBool>, [AnyHashable: Any]?) -> Void)?
|
||||
|
||||
private let completion: (UIImage?, PHAsset?) -> Void
|
||||
|
||||
private var pri_isExecuting = false {
|
||||
willSet {
|
||||
self.willChangeValue(forKey: "isExecuting")
|
||||
}
|
||||
didSet {
|
||||
self.didChangeValue(forKey: "isExecuting")
|
||||
}
|
||||
}
|
||||
|
||||
override var isExecuting: Bool {
|
||||
return pri_isExecuting
|
||||
}
|
||||
|
||||
private var pri_isFinished = false {
|
||||
willSet {
|
||||
self.willChangeValue(forKey: "isFinished")
|
||||
}
|
||||
didSet {
|
||||
self.didChangeValue(forKey: "isFinished")
|
||||
}
|
||||
}
|
||||
|
||||
override var isFinished: Bool {
|
||||
return pri_isFinished
|
||||
}
|
||||
|
||||
private var pri_isCancelled = false {
|
||||
willSet {
|
||||
willChangeValue(forKey: "isCancelled")
|
||||
}
|
||||
didSet {
|
||||
didChangeValue(forKey: "isCancelled")
|
||||
}
|
||||
}
|
||||
|
||||
private var requestImageID: PHImageRequestID = PHInvalidImageRequestID
|
||||
|
||||
override var isCancelled: Bool {
|
||||
return pri_isCancelled
|
||||
}
|
||||
|
||||
init(
|
||||
model: ZLPhotoModel,
|
||||
isOriginal: Bool,
|
||||
progress: ((CGFloat, Error?, UnsafeMutablePointer<ObjCBool>, [AnyHashable: Any]?) -> Void)? = nil,
|
||||
completion: @escaping ((UIImage?, PHAsset?) -> Void)
|
||||
) {
|
||||
self.model = model
|
||||
self.isOriginal = isOriginal
|
||||
self.progress = progress
|
||||
self.completion = completion
|
||||
super.init()
|
||||
}
|
||||
|
||||
override func start() {
|
||||
if isCancelled {
|
||||
fetchFinish()
|
||||
return
|
||||
}
|
||||
zl_debugPrint("---- start fetch")
|
||||
pri_isExecuting = true
|
||||
|
||||
// 存在编辑的图片
|
||||
if let editImage = model.editImage {
|
||||
if ZLPhotoConfiguration.default().saveNewImageAfterEdit {
|
||||
ZLPhotoManager.saveImageToAlbum(image: editImage) { [weak self] _, asset in
|
||||
self?.completion(editImage, asset)
|
||||
self?.fetchFinish()
|
||||
}
|
||||
} else {
|
||||
ZLMainAsync {
|
||||
self.completion(editImage, nil)
|
||||
self.fetchFinish()
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if ZLPhotoConfiguration.default().allowSelectGif, model.type == .gif {
|
||||
requestImageID = ZLPhotoManager.fetchOriginalImageData(for: model.asset) { [weak self] data, _, isDegraded in
|
||||
if !isDegraded {
|
||||
let image = UIImage.zl.animateGifImage(data: data)
|
||||
self?.completion(image, nil)
|
||||
self?.fetchFinish()
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if isOriginal {
|
||||
requestImageID = ZLPhotoManager.fetchOriginalImage(for: model.asset, progress: progress) { [weak self] image, isDegraded in
|
||||
if !isDegraded {
|
||||
zl_debugPrint("---- 原图加载完成 \(String(describing: self?.isCancelled))")
|
||||
self?.completion(image?.zl.fixOrientation(), nil)
|
||||
self?.fetchFinish()
|
||||
}
|
||||
}
|
||||
} else {
|
||||
requestImageID = ZLPhotoManager.fetchImage(for: model.asset, size: model.previewSize, progress: progress) { [weak self] image, isDegraded in
|
||||
if !isDegraded {
|
||||
zl_debugPrint("---- 加载完成 isCancelled: \(String(describing: self?.isCancelled))")
|
||||
self?.completion(self?.scaleImage(image?.zl.fixOrientation()), nil)
|
||||
self?.fetchFinish()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override func cancel() {
|
||||
super.cancel()
|
||||
zl_debugPrint("---- cancel \(isExecuting) \(requestImageID)")
|
||||
PHImageManager.default().cancelImageRequest(requestImageID)
|
||||
pri_isCancelled = true
|
||||
if isExecuting {
|
||||
fetchFinish()
|
||||
}
|
||||
}
|
||||
|
||||
private func scaleImage(_ image: UIImage?) -> UIImage? {
|
||||
guard let i = image else {
|
||||
return nil
|
||||
}
|
||||
guard let data = i.jpegData(compressionQuality: 1) else {
|
||||
return i
|
||||
}
|
||||
let mUnit: CGFloat = 1024 * 1024
|
||||
|
||||
if data.count < Int(0.2 * mUnit) {
|
||||
return i
|
||||
}
|
||||
let scale: CGFloat = (data.count > Int(mUnit) ? 0.6 : 0.8)
|
||||
|
||||
guard let d = i.jpegData(compressionQuality: scale) else {
|
||||
return i
|
||||
}
|
||||
return UIImage(data: d)
|
||||
}
|
||||
|
||||
private func fetchFinish() {
|
||||
pri_isExecuting = false
|
||||
pri_isFinished = true
|
||||
}
|
||||
}
|
||||
254
Pods/ZLPhotoBrowser/Sources/General/ZLGeneralDefine.swift
generated
Normal file
254
Pods/ZLPhotoBrowser/Sources/General/ZLGeneralDefine.swift
generated
Normal file
@@ -0,0 +1,254 @@
|
||||
//
|
||||
// ZLGeneralDefine.swift
|
||||
// ZLPhotoBrowser
|
||||
//
|
||||
// Created by long on 2020/8/11.
|
||||
//
|
||||
// Copyright (c) 2020 Long Zhang <495181165@qq.com>
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
// THE SOFTWARE.
|
||||
|
||||
import UIKit
|
||||
|
||||
let ZLMaxImageWidth: CGFloat = 500
|
||||
|
||||
enum ZLLayout {
|
||||
static let navTitleFont: UIFont = .zl.font(ofSize: 17)
|
||||
|
||||
static let bottomToolViewH: CGFloat = 55
|
||||
|
||||
static let bottomToolBtnH: CGFloat = 34
|
||||
|
||||
static let bottomToolBtnY: CGFloat = 10
|
||||
|
||||
static let bottomToolTitleFont: UIFont = .zl.font(ofSize: 17)
|
||||
|
||||
static let bottomToolBtnCornerRadius: CGFloat = 5
|
||||
}
|
||||
|
||||
func markSelected(source: inout [ZLPhotoModel], selected: inout [ZLPhotoModel]) {
|
||||
guard selected.count > 0 else {
|
||||
return
|
||||
}
|
||||
|
||||
var selIds: [String: Bool] = [:]
|
||||
var selEditImage: [String: UIImage] = [:]
|
||||
var selEditModel: [String: ZLEditImageModel] = [:]
|
||||
var selIdAndIndex: [String: Int] = [:]
|
||||
|
||||
for (index, m) in selected.enumerated() {
|
||||
selIds[m.ident] = true
|
||||
selEditImage[m.ident] = m.editImage
|
||||
selEditModel[m.ident] = m.editImageModel
|
||||
selIdAndIndex[m.ident] = index
|
||||
}
|
||||
|
||||
source.forEach { m in
|
||||
if selIds[m.ident] == true {
|
||||
m.isSelected = true
|
||||
m.editImage = selEditImage[m.ident]
|
||||
m.editImageModel = selEditModel[m.ident]
|
||||
selected[selIdAndIndex[m.ident]!] = m
|
||||
} else {
|
||||
m.isSelected = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func getAppName() -> String {
|
||||
if let name = Bundle.main.localizedInfoDictionary?["CFBundleDisplayName"] as? String {
|
||||
return name
|
||||
}
|
||||
if let name = Bundle.main.infoDictionary?["CFBundleDisplayName"] as? String {
|
||||
return name
|
||||
}
|
||||
if let name = Bundle.main.infoDictionary?["CFBundleName"] as? String {
|
||||
return name
|
||||
}
|
||||
return "App"
|
||||
}
|
||||
|
||||
func deviceIsiPhone() -> Bool {
|
||||
return UIDevice.current.userInterfaceIdiom == .phone
|
||||
}
|
||||
|
||||
func deviceIsiPad() -> Bool {
|
||||
return UIDevice.current.userInterfaceIdiom == .pad
|
||||
}
|
||||
|
||||
func deviceSafeAreaInsets() -> UIEdgeInsets {
|
||||
var insets: UIEdgeInsets = .zero
|
||||
|
||||
if #available(iOS 11, *) {
|
||||
insets = UIApplication.shared.keyWindow?.safeAreaInsets ?? .zero
|
||||
}
|
||||
|
||||
return insets
|
||||
}
|
||||
|
||||
func deviceIsFringeScreen() -> Bool {
|
||||
return deviceSafeAreaInsets().top > 0
|
||||
}
|
||||
|
||||
func isSmallScreen() -> Bool {
|
||||
return UIScreen.main.bounds.height <= 812
|
||||
}
|
||||
|
||||
func isRTL() -> Bool {
|
||||
return UIView.userInterfaceLayoutDirection(for: UIView.appearance().semanticContentAttribute) == .rightToLeft
|
||||
}
|
||||
|
||||
func showAlertView(_ message: String, _ sender: UIViewController?) {
|
||||
ZLMainAsync {
|
||||
let action = ZLCustomAlertAction(title: localLanguageTextValue(.ok), style: .default, handler: nil)
|
||||
showAlertController(title: nil, message: message, style: .alert, actions: [action], sender: sender)
|
||||
}
|
||||
}
|
||||
|
||||
func showAlertController(title: String?, message: String?, style: ZLCustomAlertStyle, actions: [ZLCustomAlertAction], sender: UIViewController?) {
|
||||
if let alertClass = ZLPhotoUIConfiguration.default().customAlertClass {
|
||||
let alert = alertClass.alert(title: title, message: message ?? "", style: style)
|
||||
actions.forEach { alert.addAction($0) }
|
||||
alert.show(with: sender)
|
||||
return
|
||||
}
|
||||
|
||||
let alert = UIAlertController(title: title, message: message, preferredStyle: style.toSystemAlertStyle)
|
||||
actions
|
||||
.map { $0.toSystemAlertAction() }
|
||||
.forEach { alert.addAction($0) }
|
||||
if deviceIsiPad() {
|
||||
alert.popoverPresentationController?.sourceView = sender?.view
|
||||
}
|
||||
(sender ?? UIApplication.shared.keyWindow?.rootViewController)?.zl.showAlertController(alert)
|
||||
}
|
||||
|
||||
func canAddModel(_ model: ZLPhotoModel, currentSelectCount: Int, sender: UIViewController?, showAlert: Bool = true) -> Bool {
|
||||
let config = ZLPhotoConfiguration.default()
|
||||
|
||||
guard config.canSelectAsset?(model.asset) ?? true else {
|
||||
return false
|
||||
}
|
||||
|
||||
if currentSelectCount >= config.maxSelectCount {
|
||||
if showAlert {
|
||||
let message = String(format: localLanguageTextValue(.exceededMaxSelectCount), config.maxSelectCount)
|
||||
showAlertView(message, sender)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
if currentSelectCount > 0,
|
||||
!config.allowMixSelect,
|
||||
model.type == .video{
|
||||
return false
|
||||
}
|
||||
|
||||
guard model.type == .video else {
|
||||
return true
|
||||
}
|
||||
|
||||
if model.second > config.maxSelectVideoDuration {
|
||||
if showAlert {
|
||||
let message = String(format: localLanguageTextValue(.longerThanMaxVideoDuration), config.maxSelectVideoDuration)
|
||||
showAlertView(message, sender)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
if model.second < config.minSelectVideoDuration {
|
||||
if showAlert {
|
||||
let message = String(format: localLanguageTextValue(.shorterThanMinVideoDuration), config.minSelectVideoDuration)
|
||||
showAlertView(message, sender)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
guard (config.minSelectVideoDataSize > 0 || config.maxSelectVideoDataSize != .greatestFiniteMagnitude),
|
||||
let size = model.dataSize else {
|
||||
return true
|
||||
}
|
||||
|
||||
if size > config.maxSelectVideoDataSize {
|
||||
if showAlert {
|
||||
let value = Int(round(config.maxSelectVideoDataSize / 1024))
|
||||
let message = String(format: localLanguageTextValue(.largerThanMaxVideoDataSize), String(value))
|
||||
showAlertView(message, sender)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
if size < config.minSelectVideoDataSize {
|
||||
if showAlert {
|
||||
let value = Int(round(config.minSelectVideoDataSize / 1024))
|
||||
let message = String(format: localLanguageTextValue(.smallerThanMinVideoDataSize), String(value))
|
||||
showAlertView(message, sender)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
/// Check if the video duration and size meet the requirements
|
||||
func videoIsMeetRequirements(model: ZLPhotoModel) -> Bool {
|
||||
guard model.type == .video else {
|
||||
return true
|
||||
}
|
||||
|
||||
let config = ZLPhotoConfiguration.default()
|
||||
|
||||
guard config.minSelectVideoDuration...config.maxSelectVideoDuration ~= model.second else {
|
||||
return false
|
||||
}
|
||||
|
||||
if (config.minSelectVideoDataSize > 0 || config.maxSelectVideoDataSize != .greatestFiniteMagnitude),
|
||||
let dataSize = model.dataSize,
|
||||
!(config.minSelectVideoDataSize...config.maxSelectVideoDataSize ~= dataSize) {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func ZLMainAsync(after: TimeInterval = 0, handler: @escaping (() -> Void)) {
|
||||
if after > 0 {
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + after) {
|
||||
handler()
|
||||
}
|
||||
} else {
|
||||
if Thread.isMainThread {
|
||||
handler()
|
||||
} else {
|
||||
DispatchQueue.main.async {
|
||||
handler()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func zl_debugPrint(_ message: Any...) {
|
||||
// message.forEach { debugPrint($0) }
|
||||
}
|
||||
|
||||
func zlLoggerInDebug(_ lastMessage: @autoclosure () -> String, file: StaticString = #file, line: UInt = #line) {
|
||||
#if DEBUG
|
||||
print("\(file):\(line): \(lastMessage())")
|
||||
#endif
|
||||
}
|
||||
71
Pods/ZLPhotoBrowser/Sources/General/ZLImageNavController.swift
generated
Normal file
71
Pods/ZLPhotoBrowser/Sources/General/ZLImageNavController.swift
generated
Normal file
@@ -0,0 +1,71 @@
|
||||
//
|
||||
// ZLImageNavController.swift
|
||||
// ZLPhotoBrowser
|
||||
//
|
||||
// Created by long on 2020/8/18.
|
||||
//
|
||||
// Copyright (c) 2020 Long Zhang <495181165@qq.com>
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
// THE SOFTWARE.
|
||||
|
||||
import UIKit
|
||||
import Photos
|
||||
|
||||
class ZLImageNavController: UINavigationController {
|
||||
|
||||
var isSelectedOriginal: Bool = false
|
||||
|
||||
var arrSelectedModels: [ZLPhotoModel] = []
|
||||
|
||||
var selectImageBlock: (() -> Void)?
|
||||
|
||||
var cancelBlock: (() -> Void)?
|
||||
|
||||
deinit {
|
||||
zl_debugPrint("ZLImageNavController deinit")
|
||||
}
|
||||
|
||||
override var preferredStatusBarStyle: UIStatusBarStyle {
|
||||
return ZLPhotoUIConfiguration.default().statusBarStyle
|
||||
}
|
||||
|
||||
override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) {
|
||||
super.init(nibName: nil, bundle: nil)
|
||||
}
|
||||
|
||||
override init(rootViewController: UIViewController) {
|
||||
super.init(rootViewController: rootViewController)
|
||||
navigationBar.barStyle = .black
|
||||
navigationBar.isTranslucent = true
|
||||
modalPresentationStyle = .fullScreen
|
||||
isNavigationBarHidden = true
|
||||
}
|
||||
|
||||
@available(*, unavailable)
|
||||
required init?(coder aDecoder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
|
||||
// Do any additional setup after loading the view.
|
||||
}
|
||||
|
||||
}
|
||||
582
Pods/ZLPhotoBrowser/Sources/General/ZLImagePreviewController.swift
generated
Normal file
582
Pods/ZLPhotoBrowser/Sources/General/ZLImagePreviewController.swift
generated
Normal file
@@ -0,0 +1,582 @@
|
||||
//
|
||||
// ZLImagePreviewController.swift
|
||||
// ZLPhotoBrowser
|
||||
//
|
||||
// Created by long on 2020/10/22.
|
||||
//
|
||||
// Copyright (c) 2020 Long Zhang <495181165@qq.com>
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
// THE SOFTWARE.
|
||||
|
||||
import UIKit
|
||||
import Photos
|
||||
|
||||
@objc public enum ZLURLType: Int {
|
||||
case image
|
||||
case video
|
||||
}
|
||||
|
||||
public typealias ZLImageLoaderBlock = (_ url: URL, _ imageView: UIImageView, _ progress: @escaping (CGFloat) -> Void, _ complete: @escaping () -> Void) -> Void
|
||||
|
||||
public class ZLImagePreviewController: UIViewController {
|
||||
static let colItemSpacing: CGFloat = 40
|
||||
|
||||
static let selPhotoPreviewH: CGFloat = 100
|
||||
|
||||
private let datas: [Any]
|
||||
|
||||
private var selectStatus: [Bool]
|
||||
|
||||
private let urlType: ((URL) -> ZLURLType)?
|
||||
|
||||
private let urlImageLoader: ZLImageLoaderBlock?
|
||||
|
||||
private let showSelectBtn: Bool
|
||||
|
||||
private let showBottomView: Bool
|
||||
|
||||
private var currentIndex: Int
|
||||
|
||||
private var indexBeforOrientationChanged: Int
|
||||
|
||||
private lazy var collectionView: UICollectionView = {
|
||||
let layout = ZLCollectionViewFlowLayout()
|
||||
layout.scrollDirection = .horizontal
|
||||
|
||||
let view = UICollectionView(frame: .zero, collectionViewLayout: layout)
|
||||
view.backgroundColor = .clear
|
||||
view.dataSource = self
|
||||
view.delegate = self
|
||||
view.isPagingEnabled = true
|
||||
view.showsHorizontalScrollIndicator = false
|
||||
|
||||
ZLPhotoPreviewCell.zl.register(view)
|
||||
ZLGifPreviewCell.zl.register(view)
|
||||
ZLLivePhotoPreviewCell.zl.register(view)
|
||||
ZLVideoPreviewCell.zl.register(view)
|
||||
ZLLocalImagePreviewCell.zl.register(view)
|
||||
ZLNetImagePreviewCell.zl.register(view)
|
||||
ZLNetVideoPreviewCell.zl.register(view)
|
||||
|
||||
return view
|
||||
}()
|
||||
|
||||
private lazy var navView: UIView = {
|
||||
let view = UIView()
|
||||
view.backgroundColor = .zl.navBarColorOfPreviewVC
|
||||
return view
|
||||
}()
|
||||
|
||||
private var navBlurView: UIVisualEffectView?
|
||||
|
||||
private lazy var backBtn: UIButton = {
|
||||
let btn = UIButton(type: .custom)
|
||||
var image = UIImage.zl.getImage("zl_navBack")
|
||||
if isRTL() {
|
||||
image = image?.imageFlippedForRightToLeftLayoutDirection()
|
||||
btn.imageEdgeInsets = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: -10)
|
||||
} else {
|
||||
btn.imageEdgeInsets = UIEdgeInsets(top: 0, left: -10, bottom: 0, right: 0)
|
||||
}
|
||||
btn.setImage(image, for: .normal)
|
||||
btn.addTarget(self, action: #selector(backBtnClick), for: .touchUpInside)
|
||||
return btn
|
||||
}()
|
||||
|
||||
private lazy var indexLabel: UILabel = {
|
||||
let label = UILabel()
|
||||
label.textColor = .zl.indexLabelTextColor
|
||||
label.font = ZLLayout.navTitleFont
|
||||
label.textAlignment = .center
|
||||
return label
|
||||
}()
|
||||
|
||||
private lazy var selectBtn: ZLEnlargeButton = {
|
||||
let btn = ZLEnlargeButton(type: .custom)
|
||||
btn.setImage(.zl.getImage("zl_btn_circle"), for: .normal)
|
||||
btn.setImage(.zl.getImage("zl_btn_selected"), for: .selected)
|
||||
btn.enlargeInset = 10
|
||||
btn.addTarget(self, action: #selector(selectBtnClick), for: .touchUpInside)
|
||||
return btn
|
||||
}()
|
||||
|
||||
private lazy var bottomView: UIView = {
|
||||
let view = UIView()
|
||||
view.backgroundColor = .zl.bottomToolViewBgColorOfPreviewVC
|
||||
return view
|
||||
}()
|
||||
|
||||
private var bottomBlurView: UIVisualEffectView?
|
||||
|
||||
private lazy var doneBtn: UIButton = {
|
||||
let btn = UIButton(type: .custom)
|
||||
btn.titleLabel?.font = ZLLayout.bottomToolTitleFont
|
||||
btn.setTitle(title, for: .normal)
|
||||
btn.setTitleColor(.zl.bottomToolViewDoneBtnNormalTitleColorOfPreviewVC, for: .normal)
|
||||
btn.setTitleColor(.zl.bottomToolViewDoneBtnDisableTitleColorOfPreviewVC, for: .disabled)
|
||||
btn.addTarget(self, action: #selector(doneBtnClick), for: .touchUpInside)
|
||||
btn.backgroundColor = .zl.bottomToolViewBtnNormalBgColorOfPreviewVC
|
||||
btn.layer.masksToBounds = true
|
||||
btn.layer.cornerRadius = ZLLayout.bottomToolBtnCornerRadius
|
||||
return btn
|
||||
}()
|
||||
|
||||
private var isFirstAppear = true
|
||||
|
||||
private var hideNavView = false
|
||||
|
||||
private var orientation: UIInterfaceOrientation = .unknown
|
||||
|
||||
@objc public var longPressBlock: ((ZLImagePreviewController?, UIImage?, Int) -> Void)?
|
||||
|
||||
@objc public var doneBlock: (([Any]) -> Void)?
|
||||
|
||||
@objc public var videoHttpHeader: [String: Any]?
|
||||
|
||||
override public var prefersStatusBarHidden: Bool {
|
||||
return !ZLPhotoUIConfiguration.default().showStatusBarInPreviewInterface
|
||||
}
|
||||
|
||||
override public var preferredStatusBarStyle: UIStatusBarStyle {
|
||||
return ZLPhotoUIConfiguration.default().statusBarStyle
|
||||
}
|
||||
|
||||
deinit {
|
||||
zl_debugPrint("ZLImagePreviewController deinit")
|
||||
}
|
||||
|
||||
/// - Parameters:
|
||||
/// - datas: Must be one of PHAsset, UIImage and URL, will filter others in init function.
|
||||
/// - showBottomView: If showSelectBtn is true, showBottomView is always true.
|
||||
/// - index: Index for first display.
|
||||
/// - urlType: Tell me the url is image or video.
|
||||
/// - urlImageLoader: Called when cell will display, cell will layout after callback when image load finish. The first block is progress callback, second is load finish callback.
|
||||
@objc public init(
|
||||
datas: [Any],
|
||||
index: Int = 0,
|
||||
showSelectBtn: Bool = true,
|
||||
showBottomView: Bool = true,
|
||||
urlType: ((URL) -> ZLURLType)? = nil,
|
||||
urlImageLoader: ZLImageLoaderBlock? = nil
|
||||
) {
|
||||
let filterDatas = datas.filter { $0 is PHAsset || $0 is UIImage || $0 is URL }
|
||||
self.datas = filterDatas
|
||||
selectStatus = Array(repeating: true, count: filterDatas.count)
|
||||
currentIndex = min(index, filterDatas.count - 1)
|
||||
indexBeforOrientationChanged = currentIndex
|
||||
self.showSelectBtn = showSelectBtn
|
||||
self.showBottomView = showSelectBtn ? true : showBottomView
|
||||
self.urlType = urlType
|
||||
self.urlImageLoader = urlImageLoader
|
||||
super.init(nibName: nil, bundle: nil)
|
||||
}
|
||||
|
||||
@available(*, unavailable)
|
||||
required init?(coder _: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
override public func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
|
||||
setupUI()
|
||||
resetSubViewStatus()
|
||||
}
|
||||
|
||||
override public func viewWillAppear(_ animated: Bool) {
|
||||
super.viewWillAppear(animated)
|
||||
navigationController?.navigationBar.isHidden = true
|
||||
}
|
||||
|
||||
override public func viewDidAppear(_ animated: Bool) {
|
||||
super.viewDidAppear(animated)
|
||||
|
||||
guard isFirstAppear else {
|
||||
return
|
||||
}
|
||||
isFirstAppear = false
|
||||
|
||||
reloadCurrentCell()
|
||||
}
|
||||
|
||||
override public func viewDidLayoutSubviews() {
|
||||
super.viewDidLayoutSubviews()
|
||||
|
||||
var insets = UIEdgeInsets(top: 20, left: 0, bottom: 0, right: 0)
|
||||
if #available(iOS 11.0, *) {
|
||||
insets = view.safeAreaInsets
|
||||
}
|
||||
insets.top = max(20, insets.top)
|
||||
|
||||
collectionView.frame = CGRect(
|
||||
x: -ZLPhotoPreviewController.colItemSpacing / 2,
|
||||
y: 0,
|
||||
width: view.zl.width + ZLPhotoPreviewController.colItemSpacing,
|
||||
height: view.zl.height
|
||||
)
|
||||
|
||||
let navH = insets.top + 44
|
||||
navView.frame = CGRect(x: 0, y: 0, width: view.zl.width, height: navH)
|
||||
navBlurView?.frame = navView.bounds
|
||||
|
||||
indexLabel.frame = CGRect(x: (view.zl.width - 80) / 2, y: insets.top, width: 80, height: 44)
|
||||
|
||||
if isRTL() {
|
||||
backBtn.frame = CGRect(x: view.zl.width - insets.right - 60, y: insets.top, width: 60, height: 44)
|
||||
selectBtn.frame = CGRect(x: insets.left + 15, y: insets.top + (44 - 25) / 2, width: 25, height: 25)
|
||||
} else {
|
||||
backBtn.frame = CGRect(x: insets.left, y: insets.top, width: 60, height: 44)
|
||||
selectBtn.frame = CGRect(x: view.zl.width - 40 - insets.right, y: insets.top + (44 - 25) / 2, width: 25, height: 25)
|
||||
}
|
||||
|
||||
let bottomViewH = ZLLayout.bottomToolViewH
|
||||
|
||||
bottomView.frame = CGRect(x: 0, y: view.zl.height - insets.bottom - bottomViewH, width: view.zl.width, height: bottomViewH + insets.bottom)
|
||||
bottomBlurView?.frame = bottomView.bounds
|
||||
|
||||
resetBottomViewFrame()
|
||||
|
||||
let ori = UIApplication.shared.statusBarOrientation
|
||||
if ori != orientation {
|
||||
orientation = ori
|
||||
collectionView.setContentOffset(
|
||||
CGPoint(
|
||||
x: (view.zl.width + ZLPhotoPreviewController.colItemSpacing) * CGFloat(indexBeforOrientationChanged),
|
||||
y: 0
|
||||
),
|
||||
animated: false
|
||||
)
|
||||
collectionView.performBatchUpdates({
|
||||
self.collectionView.setContentOffset(
|
||||
CGPoint(
|
||||
x: (self.view.frame.width + ZLPhotoPreviewController.colItemSpacing) * CGFloat(self.indexBeforOrientationChanged),
|
||||
y: 0
|
||||
),
|
||||
animated: false
|
||||
)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
private func reloadCurrentCell() {
|
||||
guard let cell = collectionView.cellForItem(at: IndexPath(row: currentIndex, section: 0)) else {
|
||||
return
|
||||
}
|
||||
|
||||
if let cell = cell as? ZLGifPreviewCell {
|
||||
cell.loadGifWhenCellDisplaying()
|
||||
} else if let cell = cell as? ZLLivePhotoPreviewCell {
|
||||
cell.loadLivePhotoData()
|
||||
}
|
||||
}
|
||||
|
||||
private func setupUI() {
|
||||
view.backgroundColor = .zl.previewVCBgColor
|
||||
automaticallyAdjustsScrollViewInsets = false
|
||||
|
||||
view.addSubview(navView)
|
||||
|
||||
if let effect = ZLPhotoUIConfiguration.default().navViewBlurEffectOfPreview {
|
||||
navBlurView = UIVisualEffectView(effect: effect)
|
||||
navView.addSubview(navBlurView!)
|
||||
}
|
||||
|
||||
navView.addSubview(backBtn)
|
||||
navView.addSubview(indexLabel)
|
||||
navView.addSubview(selectBtn)
|
||||
view.addSubview(collectionView)
|
||||
view.addSubview(bottomView)
|
||||
|
||||
if let effect = ZLPhotoUIConfiguration.default().bottomViewBlurEffectOfPreview {
|
||||
bottomBlurView = UIVisualEffectView(effect: effect)
|
||||
bottomView.addSubview(bottomBlurView!)
|
||||
}
|
||||
|
||||
bottomView.addSubview(doneBtn)
|
||||
view.bringSubviewToFront(navView)
|
||||
}
|
||||
|
||||
private func resetSubViewStatus() {
|
||||
indexLabel.text = String(currentIndex + 1) + " / " + String(datas.count)
|
||||
|
||||
if showSelectBtn {
|
||||
selectBtn.isSelected = selectStatus[currentIndex]
|
||||
} else {
|
||||
selectBtn.isHidden = true
|
||||
}
|
||||
|
||||
resetBottomViewFrame()
|
||||
}
|
||||
|
||||
private func resetBottomViewFrame() {
|
||||
guard showBottomView else {
|
||||
bottomView.isHidden = true
|
||||
return
|
||||
}
|
||||
|
||||
let btnY = ZLLayout.bottomToolBtnY
|
||||
|
||||
var doneTitle = localLanguageTextValue(.done)
|
||||
let selCount = selectStatus.filter { $0 }.count
|
||||
if showSelectBtn,
|
||||
ZLPhotoConfiguration.default().showSelectCountOnDoneBtn,
|
||||
selCount > 0 {
|
||||
doneTitle += "(" + String(selCount) + ")"
|
||||
}
|
||||
let doneBtnW = doneTitle.zl.boundingRect(font: ZLLayout.bottomToolTitleFont, limitSize: CGSize(width: CGFloat.greatestFiniteMagnitude, height: 30)).width + 20
|
||||
doneBtn.frame = CGRect(x: bottomView.bounds.width - doneBtnW - 15, y: btnY, width: doneBtnW, height: ZLLayout.bottomToolBtnH)
|
||||
doneBtn.setTitle(doneTitle, for: .normal)
|
||||
}
|
||||
|
||||
private func dismiss() {
|
||||
if let nav = navigationController {
|
||||
let vc = nav.popViewController(animated: true)
|
||||
if vc == nil {
|
||||
nav.dismiss(animated: true, completion: nil)
|
||||
}
|
||||
} else {
|
||||
dismiss(animated: true, completion: nil)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: btn actions
|
||||
|
||||
@objc private func backBtnClick() {
|
||||
dismiss()
|
||||
}
|
||||
|
||||
@objc private func selectBtnClick() {
|
||||
var isSelected = selectStatus[currentIndex]
|
||||
selectBtn.layer.removeAllAnimations()
|
||||
if isSelected {
|
||||
isSelected = false
|
||||
} else {
|
||||
if ZLPhotoConfiguration.default().animateSelectBtnWhenSelect {
|
||||
selectBtn.layer.add(ZLAnimationUtils.springAnimation(), forKey: nil)
|
||||
}
|
||||
isSelected = true
|
||||
}
|
||||
|
||||
selectStatus[currentIndex] = isSelected
|
||||
resetSubViewStatus()
|
||||
}
|
||||
|
||||
@objc private func doneBtnClick() {
|
||||
if showSelectBtn {
|
||||
let res = datas.enumerated()
|
||||
.filter { self.selectStatus[$0.offset] }
|
||||
.map { $0.element }
|
||||
|
||||
doneBlock?(res)
|
||||
} else {
|
||||
doneBlock?(datas)
|
||||
}
|
||||
|
||||
dismiss()
|
||||
}
|
||||
|
||||
private func tapPreviewCell() {
|
||||
hideNavView.toggle()
|
||||
|
||||
let cell = collectionView.cellForItem(at: IndexPath(row: currentIndex, section: 0))
|
||||
if let cell = cell as? ZLVideoPreviewCell, cell.isPlaying {
|
||||
hideNavView = true
|
||||
}
|
||||
navView.isHidden = hideNavView
|
||||
if showBottomView {
|
||||
bottomView.isHidden = hideNavView
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// scroll view delegate
|
||||
public extension ZLImagePreviewController {
|
||||
func scrollViewDidScroll(_ scrollView: UIScrollView) {
|
||||
guard scrollView == collectionView else {
|
||||
return
|
||||
}
|
||||
|
||||
NotificationCenter.default.post(name: ZLPhotoPreviewController.previewVCScrollNotification, object: nil)
|
||||
let offset = scrollView.contentOffset
|
||||
var page = Int(round(offset.x / (view.bounds.width + ZLPhotoPreviewController.colItemSpacing)))
|
||||
page = max(0, min(page, datas.count - 1))
|
||||
if page == currentIndex {
|
||||
return
|
||||
}
|
||||
|
||||
currentIndex = page
|
||||
resetSubViewStatus()
|
||||
}
|
||||
|
||||
func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
|
||||
indexBeforOrientationChanged = currentIndex
|
||||
let cell = collectionView.cellForItem(at: IndexPath(row: currentIndex, section: 0))
|
||||
if let cell = cell as? ZLGifPreviewCell {
|
||||
cell.loadGifWhenCellDisplaying()
|
||||
} else if let cell = cell as? ZLLivePhotoPreviewCell {
|
||||
cell.loadLivePhotoData()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension ZLImagePreviewController: UICollectionViewDataSource, UICollectionViewDelegateFlowLayout {
|
||||
public func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumInteritemSpacingForSectionAt section: Int) -> CGFloat {
|
||||
return ZLImagePreviewController.colItemSpacing
|
||||
}
|
||||
|
||||
public func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat {
|
||||
return ZLImagePreviewController.colItemSpacing
|
||||
}
|
||||
|
||||
public func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets {
|
||||
return UIEdgeInsets(top: 0, left: ZLImagePreviewController.colItemSpacing / 2, bottom: 0, right: ZLImagePreviewController.colItemSpacing / 2)
|
||||
}
|
||||
|
||||
public func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
|
||||
return CGSize(width: view.zl.width, height: view.zl.height)
|
||||
}
|
||||
|
||||
public func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
|
||||
return datas.count
|
||||
}
|
||||
|
||||
public func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
|
||||
let config = ZLPhotoConfiguration.default()
|
||||
let obj = datas[indexPath.row]
|
||||
|
||||
let baseCell: ZLPreviewBaseCell
|
||||
|
||||
if let asset = obj as? PHAsset {
|
||||
let model = ZLPhotoModel(asset: asset)
|
||||
|
||||
if config.allowSelectGif, model.type == .gif {
|
||||
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: ZLGifPreviewCell.zl.identifier, for: indexPath) as! ZLGifPreviewCell
|
||||
|
||||
cell.singleTapBlock = { [weak self] in
|
||||
self?.tapPreviewCell()
|
||||
}
|
||||
|
||||
cell.model = model
|
||||
baseCell = cell
|
||||
} else if config.allowSelectLivePhoto, model.type == .livePhoto {
|
||||
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: ZLLivePhotoPreviewCell.zl.identifier, for: indexPath) as! ZLLivePhotoPreviewCell
|
||||
|
||||
cell.model = model
|
||||
|
||||
baseCell = cell
|
||||
} else if config.allowSelectVideo, model.type == .video {
|
||||
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: ZLVideoPreviewCell.zl.identifier, for: indexPath) as! ZLVideoPreviewCell
|
||||
|
||||
cell.model = model
|
||||
|
||||
baseCell = cell
|
||||
} else {
|
||||
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: ZLPhotoPreviewCell.zl.identifier, for: indexPath) as! ZLPhotoPreviewCell
|
||||
|
||||
cell.singleTapBlock = { [weak self] in
|
||||
self?.tapPreviewCell()
|
||||
}
|
||||
|
||||
cell.model = model
|
||||
|
||||
baseCell = cell
|
||||
}
|
||||
|
||||
return baseCell
|
||||
} else if let image = obj as? UIImage {
|
||||
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: ZLLocalImagePreviewCell.zl.identifier, for: indexPath) as! ZLLocalImagePreviewCell
|
||||
|
||||
cell.image = image
|
||||
|
||||
baseCell = cell
|
||||
} else if let url = obj as? URL {
|
||||
let type: ZLURLType = urlType?(url) ?? .image
|
||||
if type == .image {
|
||||
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: ZLNetImagePreviewCell.zl.identifier, for: indexPath) as! ZLNetImagePreviewCell
|
||||
cell.image = nil
|
||||
|
||||
urlImageLoader?(url, cell.preview.imageView, { [weak cell] progress in
|
||||
ZLMainAsync {
|
||||
cell?.progress = progress
|
||||
}
|
||||
}, { [weak cell] in
|
||||
ZLMainAsync {
|
||||
cell?.preview.resetSubViewSize()
|
||||
}
|
||||
})
|
||||
|
||||
baseCell = cell
|
||||
} else {
|
||||
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: ZLNetVideoPreviewCell.zl.identifier, for: indexPath) as! ZLNetVideoPreviewCell
|
||||
|
||||
cell.configureCell(videoUrl: url, httpHeader: videoHttpHeader)
|
||||
|
||||
baseCell = cell
|
||||
}
|
||||
} else {
|
||||
#if DEBUG
|
||||
fatalError("Preview obj must one of PHAsset, UIImage, URL")
|
||||
#else
|
||||
return UICollectionViewCell()
|
||||
#endif
|
||||
}
|
||||
|
||||
baseCell.singleTapBlock = { [weak self] in
|
||||
self?.tapPreviewCell()
|
||||
}
|
||||
|
||||
(baseCell as? ZLLocalImagePreviewCell)?.longPressBlock = { [weak self, weak baseCell] in
|
||||
if let callback = self?.longPressBlock {
|
||||
callback(self, baseCell?.currentImage, indexPath.row)
|
||||
} else {
|
||||
self?.showSaveImageAlert()
|
||||
}
|
||||
}
|
||||
|
||||
return baseCell
|
||||
}
|
||||
|
||||
public func collectionView(_ collectionView: UICollectionView, didEndDisplaying cell: UICollectionViewCell, forItemAt indexPath: IndexPath) {
|
||||
if let cell = cell as? ZLPreviewBaseCell {
|
||||
cell.resetSubViewStatusWhenCellEndDisplay()
|
||||
}
|
||||
}
|
||||
|
||||
private func showSaveImageAlert() {
|
||||
func saveImage() {
|
||||
guard let cell = collectionView.cellForItem(at: IndexPath(row: currentIndex, section: 0)) as? ZLLocalImagePreviewCell, let image = cell.currentImage else {
|
||||
return
|
||||
}
|
||||
|
||||
let hud = ZLProgressHUD.show()
|
||||
ZLPhotoManager.saveImageToAlbum(image: image) { [weak self] suc, _ in
|
||||
hud.hide()
|
||||
if !suc {
|
||||
showAlertView(localLanguageTextValue(.saveImageError), self)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let saveAction = ZLCustomAlertAction(title: localLanguageTextValue(.save), style: .default) { _ in
|
||||
saveImage()
|
||||
}
|
||||
let cancelAction = ZLCustomAlertAction(title: localLanguageTextValue(.cancel), style: .cancel, handler: nil)
|
||||
showAlertController(title: nil, message: "", style: .actionSheet, actions: [saveAction, cancelAction], sender: self)
|
||||
}
|
||||
}
|
||||
322
Pods/ZLPhotoBrowser/Sources/General/ZLLanguageDefine.swift
generated
Normal file
322
Pods/ZLPhotoBrowser/Sources/General/ZLLanguageDefine.swift
generated
Normal file
@@ -0,0 +1,322 @@
|
||||
//
|
||||
// ZLLanguageDefine.swift
|
||||
// ZLPhotoBrowser
|
||||
//
|
||||
// Created by long on 2020/8/17.
|
||||
//
|
||||
// Copyright (c) 2020 Long Zhang <495181165@qq.com>
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
// THE SOFTWARE.
|
||||
|
||||
import Foundation
|
||||
|
||||
@objc public enum ZLLanguageType: Int {
|
||||
case system
|
||||
case chineseSimplified
|
||||
case chineseTraditional
|
||||
case english
|
||||
case japanese
|
||||
case french
|
||||
case german
|
||||
case russian
|
||||
case vietnamese
|
||||
case korean
|
||||
case malay
|
||||
case italian
|
||||
case indonesian
|
||||
case portuguese
|
||||
case spanish
|
||||
case turkish
|
||||
case arabic
|
||||
|
||||
var key: String {
|
||||
var key = "en"
|
||||
|
||||
switch self {
|
||||
case .system:
|
||||
key = Locale.preferredLanguages.first ?? "en"
|
||||
|
||||
if key.hasPrefix("zh") {
|
||||
if key.range(of: "Hans") != nil {
|
||||
key = "zh-Hans"
|
||||
} else {
|
||||
key = "zh-Hant"
|
||||
}
|
||||
} else if key.hasPrefix("ja") {
|
||||
key = "ja-US"
|
||||
} else if key.hasPrefix("fr") {
|
||||
key = "fr"
|
||||
} else if key.hasPrefix("de") {
|
||||
key = "de"
|
||||
} else if key.hasPrefix("ru") {
|
||||
key = "ru"
|
||||
} else if key.hasPrefix("vi") {
|
||||
key = "vi"
|
||||
} else if key.hasPrefix("ko") {
|
||||
key = "ko"
|
||||
} else if key.hasPrefix("ms") {
|
||||
key = "ms"
|
||||
} else if key.hasPrefix("it") {
|
||||
key = "it"
|
||||
} else if key.hasPrefix("id") {
|
||||
key = "id"
|
||||
} else if key.hasPrefix("pt") {
|
||||
key = "pt-BR"
|
||||
} else if key.hasPrefix("es") {
|
||||
key = "es-419"
|
||||
} else if key.hasPrefix("tr") {
|
||||
key = "tr"
|
||||
} else if key.hasPrefix("ar") {
|
||||
key = "ar"
|
||||
} else {
|
||||
key = "en"
|
||||
}
|
||||
case .chineseSimplified:
|
||||
key = "zh-Hans"
|
||||
case .chineseTraditional:
|
||||
key = "zh-Hant"
|
||||
case .english:
|
||||
key = "en"
|
||||
case .japanese:
|
||||
key = "ja-US"
|
||||
case .french:
|
||||
key = "fr"
|
||||
case .german:
|
||||
key = "de"
|
||||
case .russian:
|
||||
key = "ru"
|
||||
case .vietnamese:
|
||||
key = "vi"
|
||||
case .korean:
|
||||
key = "ko"
|
||||
case .malay:
|
||||
key = "ms"
|
||||
case .italian:
|
||||
key = "it"
|
||||
case .indonesian:
|
||||
key = "id"
|
||||
case .portuguese:
|
||||
key = "pt-BR"
|
||||
case .spanish:
|
||||
key = "es-419"
|
||||
case .turkish:
|
||||
key = "tr"
|
||||
case .arabic:
|
||||
key = "ar"
|
||||
}
|
||||
|
||||
return key
|
||||
}
|
||||
}
|
||||
|
||||
public struct ZLLocalLanguageKey: Hashable {
|
||||
public let rawValue: String
|
||||
|
||||
public init(rawValue: String) {
|
||||
self.rawValue = rawValue
|
||||
}
|
||||
|
||||
/// Camera (拍照)
|
||||
public static let previewCamera = ZLLocalLanguageKey(rawValue: "previewCamera")
|
||||
|
||||
/// Record (拍摄)
|
||||
public static let previewCameraRecord = ZLLocalLanguageKey(rawValue: "previewCameraRecord")
|
||||
|
||||
/// Album (相册)
|
||||
public static let previewAlbum = ZLLocalLanguageKey(rawValue: "previewAlbum")
|
||||
|
||||
/// Cancel (取消)
|
||||
public static let cancel = ZLLocalLanguageKey(rawValue: "cancel")
|
||||
|
||||
/// No Photo (无照片)
|
||||
public static let noPhotoTips = ZLLocalLanguageKey(rawValue: "noPhotoTips")
|
||||
|
||||
/// waiting... (正在处理...)
|
||||
public static let hudLoading = ZLLocalLanguageKey(rawValue: "hudLoading")
|
||||
|
||||
/// Done (确定)
|
||||
public static let done = ZLLocalLanguageKey(rawValue: "done")
|
||||
|
||||
/// OK (确定)
|
||||
public static let ok = ZLLocalLanguageKey(rawValue: "ok")
|
||||
|
||||
/// Request timed out (请求超时)
|
||||
public static let timeout = ZLLocalLanguageKey(rawValue: "timeout")
|
||||
|
||||
/// Please Allow %@ to access your album in \"Settings\"->\"Privacy\"->\"Photos\"
|
||||
/// (请在iPhone的\"设置-隐私-照片\"选项中,允许%@访问你的照片)
|
||||
public static let noPhotoLibratyAuthority = ZLLocalLanguageKey(rawValue: "noPhotoLibratyAuthority")
|
||||
|
||||
/// Please allow %@ to access your device's camera in \"Settings\"->\"Privacy\"->\"Camera\"
|
||||
/// (请在iPhone的\"设置-隐私-相机\"选项中,允许%@访问你的相机)
|
||||
public static let noCameraAuthority = ZLLocalLanguageKey(rawValue: "noCameraAuthority")
|
||||
|
||||
/// Unable to record audio. Go to \"Settings\" > \"%@\" and enable microphone access.
|
||||
/// (无法录制声音,前往\"设置 > %@\"中打开麦克风权限)
|
||||
public static let noMicrophoneAuthority = ZLLocalLanguageKey(rawValue: "noMicrophoneAuthority")
|
||||
|
||||
/// Camera is unavailable (相机不可用)
|
||||
public static let cameraUnavailable = ZLLocalLanguageKey(rawValue: "cameraUnavailable")
|
||||
|
||||
/// Keep Recording (继续拍摄)
|
||||
public static let keepRecording = ZLLocalLanguageKey(rawValue: "keepRecording")
|
||||
|
||||
/// Go to Settings (前往设置)
|
||||
public static let gotoSettings = ZLLocalLanguageKey(rawValue: "gotoSettings")
|
||||
|
||||
/// Photos (照片)
|
||||
public static let photo = ZLLocalLanguageKey(rawValue: "photo")
|
||||
|
||||
/// Full Image (原图)
|
||||
public static let originalPhoto = ZLLocalLanguageKey(rawValue: "originalPhoto")
|
||||
|
||||
/// Back (返回)
|
||||
public static let back = ZLLocalLanguageKey(rawValue: "back")
|
||||
|
||||
/// Edit (编辑)
|
||||
public static let edit = ZLLocalLanguageKey(rawValue: "edit")
|
||||
|
||||
/// Done (完成)
|
||||
public static let editFinish = ZLLocalLanguageKey(rawValue: "editFinish")
|
||||
|
||||
/// Undo (还原)
|
||||
public static let revert = ZLLocalLanguageKey(rawValue: "revert")
|
||||
|
||||
/// Brightness (亮度)
|
||||
public static let brightness = ZLLocalLanguageKey(rawValue: "brightness")
|
||||
|
||||
/// Contrast (对比度)
|
||||
public static let contrast = ZLLocalLanguageKey(rawValue: "contrast")
|
||||
|
||||
/// Saturation (饱和度)
|
||||
public static let saturation = ZLLocalLanguageKey(rawValue: "saturation")
|
||||
|
||||
/// Preview (预览)
|
||||
public static let preview = ZLLocalLanguageKey(rawValue: "preview")
|
||||
|
||||
/// Save (保存)
|
||||
public static let save = ZLLocalLanguageKey(rawValue: "save")
|
||||
|
||||
/// Failed to save the image (图片保存失败)
|
||||
public static let saveImageError = ZLLocalLanguageKey(rawValue: "saveImageError")
|
||||
|
||||
/// Failed to save the video (视频保存失败)
|
||||
public static let saveVideoError = ZLLocalLanguageKey(rawValue: "saveVideoError")
|
||||
|
||||
/// Max select count: %ld (最多只能选择%ld张图片)
|
||||
public static let exceededMaxSelectCount = ZLLocalLanguageKey(rawValue: "exceededMaxSelectCount")
|
||||
|
||||
/// Max count for video selection: %ld (最多只能选择%ld个视频)
|
||||
public static let exceededMaxVideoSelectCount = ZLLocalLanguageKey(rawValue: "exceededMaxVideoSelectCount")
|
||||
|
||||
/// Min count for video selection: %ld (最少选择%ld个视频)
|
||||
public static let lessThanMinVideoSelectCount = ZLLocalLanguageKey(rawValue: "lessThanMinVideoSelectCount")
|
||||
|
||||
/// Can't select videos longer than %lds
|
||||
/// (不能选择超过%ld秒的视频)
|
||||
public static let longerThanMaxVideoDuration = ZLLocalLanguageKey(rawValue: "longerThanMaxVideoDuration")
|
||||
|
||||
/// Can't select videos shorter than %lds
|
||||
/// (不能选择低于%ld秒的视频)
|
||||
public static let shorterThanMinVideoDuration = ZLLocalLanguageKey(rawValue: "shorterThanMinVideoDuration")
|
||||
|
||||
/// Can't select videos larger than %@MB
|
||||
/// (不能选择大于%@MB的视频)
|
||||
public static let largerThanMaxVideoDataSize = ZLLocalLanguageKey(rawValue: "largerThanMaxVideoDataSize")
|
||||
|
||||
/// Can't select videos smaller than %@MB
|
||||
/// (不能选择小于%@MB的视频)
|
||||
public static let smallerThanMinVideoDataSize = ZLLocalLanguageKey(rawValue: "smallerThanMinVideoDataSize")
|
||||
|
||||
/// Unable to sync from iCloud (iCloud无法同步)
|
||||
public static let iCloudVideoLoadFaild = ZLLocalLanguageKey(rawValue: "iCloudVideoLoadFaild")
|
||||
|
||||
/// loading failed (图片加载失败)
|
||||
public static let imageLoadFailed = ZLLocalLanguageKey(rawValue: "imageLoadFailed")
|
||||
|
||||
/// Tap to take photo and hold to record video (轻触拍照,按住摄像)
|
||||
public static let customCameraTips = ZLLocalLanguageKey(rawValue: "customCameraTips")
|
||||
|
||||
/// Tap to take photo (轻触拍照)
|
||||
public static let customCameraTakePhotoTips = ZLLocalLanguageKey(rawValue: "customCameraTakePhotoTips")
|
||||
|
||||
/// hold to record video (按住摄像)
|
||||
public static let customCameraRecordVideoTips = ZLLocalLanguageKey(rawValue: "customCameraRecordVideoTips")
|
||||
|
||||
/// Record at least %lds (至少录制%ld秒)
|
||||
public static let minRecordTimeTips = ZLLocalLanguageKey(rawValue: "minRecordTimeTips")
|
||||
|
||||
/// Recents (所有照片)
|
||||
public static let cameraRoll = ZLLocalLanguageKey(rawValue: "cameraRoll")
|
||||
|
||||
/// Panoramas (全景照片)
|
||||
public static let panoramas = ZLLocalLanguageKey(rawValue: "panoramas")
|
||||
|
||||
/// Videos (视频)
|
||||
public static let videos = ZLLocalLanguageKey(rawValue: "videos")
|
||||
|
||||
/// Favorites (个人收藏)
|
||||
public static let favorites = ZLLocalLanguageKey(rawValue: "favorites")
|
||||
|
||||
/// Time-Lapse (延时摄影)
|
||||
public static let timelapses = ZLLocalLanguageKey(rawValue: "timelapses")
|
||||
|
||||
/// Recently Added (最近添加)
|
||||
public static let recentlyAdded = ZLLocalLanguageKey(rawValue: "recentlyAdded")
|
||||
|
||||
/// Bursts (连拍快照)
|
||||
public static let bursts = ZLLocalLanguageKey(rawValue: "bursts")
|
||||
|
||||
/// Slo-mo (慢动作)
|
||||
public static let slomoVideos = ZLLocalLanguageKey(rawValue: "slomoVideos")
|
||||
|
||||
/// Selfies (自拍)
|
||||
public static let selfPortraits = ZLLocalLanguageKey(rawValue: "selfPortraits")
|
||||
|
||||
/// Screenshots (屏幕快照)
|
||||
public static let screenshots = ZLLocalLanguageKey(rawValue: "screenshots")
|
||||
|
||||
/// Portrait (人像)
|
||||
public static let depthEffect = ZLLocalLanguageKey(rawValue: "depthEffect")
|
||||
|
||||
/// Live Photo
|
||||
public static let livePhotos = ZLLocalLanguageKey(rawValue: "livePhotos")
|
||||
|
||||
/// Animated (动图)
|
||||
public static let animated = ZLLocalLanguageKey(rawValue: "animated")
|
||||
|
||||
/// My Photo Stream (我的照片流)
|
||||
public static let myPhotoStream = ZLLocalLanguageKey(rawValue: "myPhotoStream")
|
||||
|
||||
/// All Photos (所有照片)
|
||||
public static let noTitleAlbumListPlaceholder = ZLLocalLanguageKey(rawValue: "noTitleAlbumListPlaceholder")
|
||||
|
||||
/// Unable to access all photos, go to settings (无法访问所有照片,前往设置)
|
||||
public static let unableToAccessAllPhotos = ZLLocalLanguageKey(rawValue: "unableToAccessAllPhotos")
|
||||
|
||||
/// Drag here to remove (拖到此处删除)
|
||||
public static let textStickerRemoveTips = ZLLocalLanguageKey(rawValue: "textStickerRemoveTips")
|
||||
}
|
||||
|
||||
func localLanguageTextValue(_ key: ZLLocalLanguageKey) -> String {
|
||||
if let value = ZLCustomLanguageDeploy.deploy[key] {
|
||||
return value
|
||||
}
|
||||
return Bundle.zlLocalizedString(key.rawValue)
|
||||
}
|
||||
75
Pods/ZLPhotoBrowser/Sources/General/ZLPhotoBrowser.swift
generated
Normal file
75
Pods/ZLPhotoBrowser/Sources/General/ZLPhotoBrowser.swift
generated
Normal file
@@ -0,0 +1,75 @@
|
||||
//
|
||||
// ZLPhotoBrowser.swift
|
||||
// ZLPhotoBrowser
|
||||
//
|
||||
// Created by long on 2020/9/2.
|
||||
//
|
||||
// Copyright (c) 2020 Long Zhang <495181165@qq.com>
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
// THE SOFTWARE.
|
||||
|
||||
import UIKit
|
||||
import Foundation
|
||||
import Photos
|
||||
|
||||
let version = "4.4.3.2"
|
||||
|
||||
public struct ZLPhotoBrowserWrapper<Base> {
|
||||
public let base: Base
|
||||
|
||||
public init(_ base: Base) {
|
||||
self.base = base
|
||||
}
|
||||
}
|
||||
|
||||
public protocol ZLPhotoBrowserCompatible: AnyObject { }
|
||||
|
||||
public protocol ZLPhotoBrowserCompatibleValue { }
|
||||
|
||||
extension ZLPhotoBrowserCompatible {
|
||||
public var zl: ZLPhotoBrowserWrapper<Self> {
|
||||
get { ZLPhotoBrowserWrapper(self) }
|
||||
set { }
|
||||
}
|
||||
|
||||
public static var zl: ZLPhotoBrowserWrapper<Self>.Type {
|
||||
get { ZLPhotoBrowserWrapper<Self>.self }
|
||||
set { }
|
||||
}
|
||||
}
|
||||
|
||||
extension ZLPhotoBrowserCompatibleValue {
|
||||
public var zl: ZLPhotoBrowserWrapper<Self> {
|
||||
get { ZLPhotoBrowserWrapper(self) }
|
||||
set { }
|
||||
}
|
||||
}
|
||||
|
||||
extension UIViewController: ZLPhotoBrowserCompatible { }
|
||||
extension UIColor: ZLPhotoBrowserCompatible { }
|
||||
extension UIImage: ZLPhotoBrowserCompatible { }
|
||||
extension CIImage: ZLPhotoBrowserCompatible { }
|
||||
extension PHAsset: ZLPhotoBrowserCompatible { }
|
||||
extension UIFont: ZLPhotoBrowserCompatible { }
|
||||
extension UIView: ZLPhotoBrowserCompatible { }
|
||||
|
||||
extension Array: ZLPhotoBrowserCompatibleValue { }
|
||||
extension String: ZLPhotoBrowserCompatibleValue { }
|
||||
extension CGFloat: ZLPhotoBrowserCompatibleValue { }
|
||||
extension Bool: ZLPhotoBrowserCompatibleValue { }
|
||||
366
Pods/ZLPhotoBrowser/Sources/General/ZLPhotoConfiguration+Chaining.swift
generated
Normal file
366
Pods/ZLPhotoBrowser/Sources/General/ZLPhotoConfiguration+Chaining.swift
generated
Normal file
@@ -0,0 +1,366 @@
|
||||
//
|
||||
// ZLPhotoConfiguration+Chaining.swift
|
||||
// ZLPhotoBrowser
|
||||
//
|
||||
// Created by long on 2021/11/1.
|
||||
//
|
||||
// Copyright (c) 2020 Long Zhang <495181165@qq.com>
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
// THE SOFTWARE.
|
||||
|
||||
import UIKit
|
||||
import Photos
|
||||
|
||||
public extension ZLPhotoConfiguration {
|
||||
@discardableResult
|
||||
func sortAscending(_ ascending: Bool) -> ZLPhotoConfiguration {
|
||||
sortAscending = ascending
|
||||
return self
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
func maxSelectCount(_ count: Int) -> ZLPhotoConfiguration {
|
||||
maxSelectCount = count
|
||||
return self
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
func maxVideoSelectCount(_ count: Int) -> ZLPhotoConfiguration {
|
||||
maxVideoSelectCount = count
|
||||
return self
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
func minVideoSelectCount(_ count: Int) -> ZLPhotoConfiguration {
|
||||
minVideoSelectCount = count
|
||||
return self
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
func allowMixSelect(_ value: Bool) -> ZLPhotoConfiguration {
|
||||
allowMixSelect = value
|
||||
return self
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
func maxPreviewCount(_ count: Int) -> ZLPhotoConfiguration {
|
||||
maxPreviewCount = count
|
||||
return self
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
func allowSelectImage(_ value: Bool) -> ZLPhotoConfiguration {
|
||||
allowSelectImage = value
|
||||
return self
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
@objc func allowSelectVideo(_ value: Bool) -> ZLPhotoConfiguration {
|
||||
allowSelectVideo = value
|
||||
return self
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
func allowSelectGif(_ value: Bool) -> ZLPhotoConfiguration {
|
||||
allowSelectGif = value
|
||||
return self
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
func allowSelectLivePhoto(_ value: Bool) -> ZLPhotoConfiguration {
|
||||
allowSelectLivePhoto = value
|
||||
return self
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
func allowTakePhotoInLibrary(_ value: Bool) -> ZLPhotoConfiguration {
|
||||
allowTakePhotoInLibrary = value
|
||||
return self
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
func callbackDirectlyAfterTakingPhoto(_ value: Bool) -> ZLPhotoConfiguration {
|
||||
callbackDirectlyAfterTakingPhoto = value
|
||||
return self
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
func allowEditImage(_ value: Bool) -> ZLPhotoConfiguration {
|
||||
allowEditImage = value
|
||||
return self
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
func allowEditVideo(_ value: Bool) -> ZLPhotoConfiguration {
|
||||
allowEditVideo = value
|
||||
return self
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
func animateSelectBtnWhenSelect(_ animate: Bool) -> ZLPhotoConfiguration {
|
||||
animateSelectBtnWhenSelect = animate
|
||||
return self
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
func selectBtnAnimationDuration(_ duration: CFTimeInterval) -> ZLPhotoConfiguration {
|
||||
selectBtnAnimationDuration = duration
|
||||
return self
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
func editAfterSelectThumbnailImage(_ value: Bool) -> ZLPhotoConfiguration {
|
||||
editAfterSelectThumbnailImage = value
|
||||
return self
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
func cropVideoAfterSelectThumbnail(_ value: Bool) -> ZLPhotoConfiguration {
|
||||
cropVideoAfterSelectThumbnail = value
|
||||
return self
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
func showClipDirectlyIfOnlyHasClipTool(_ value: Bool) -> ZLPhotoConfiguration {
|
||||
showClipDirectlyIfOnlyHasClipTool = value
|
||||
return self
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
func saveNewImageAfterEdit(_ value: Bool) -> ZLPhotoConfiguration {
|
||||
saveNewImageAfterEdit = value
|
||||
return self
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
func allowSlideSelect(_ value: Bool) -> ZLPhotoConfiguration {
|
||||
allowSlideSelect = value
|
||||
return self
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
func autoScrollWhenSlideSelectIsActive(_ value: Bool) -> ZLPhotoConfiguration {
|
||||
autoScrollWhenSlideSelectIsActive = value
|
||||
return self
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
func autoScrollMaxSpeed(_ speed: CGFloat) -> ZLPhotoConfiguration {
|
||||
autoScrollMaxSpeed = speed
|
||||
return self
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
func allowDragSelect(_ value: Bool) -> ZLPhotoConfiguration {
|
||||
allowDragSelect = value
|
||||
return self
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
func allowSelectOriginal(_ value: Bool) -> ZLPhotoConfiguration {
|
||||
allowSelectOriginal = value
|
||||
return self
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
func alwaysRequestOriginal(_ value: Bool) -> ZLPhotoConfiguration {
|
||||
alwaysRequestOriginal = value
|
||||
return self
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
func allowPreviewPhotos(_ value: Bool) -> ZLPhotoConfiguration {
|
||||
allowPreviewPhotos = value
|
||||
return self
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
func showPreviewButtonInAlbum(_ value: Bool) -> ZLPhotoConfiguration {
|
||||
showPreviewButtonInAlbum = value
|
||||
return self
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
func showSelectCountOnDoneBtn(_ value: Bool) -> ZLPhotoConfiguration {
|
||||
showSelectCountOnDoneBtn = value
|
||||
return self
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
func maxEditVideoTime(_ second: Second) -> ZLPhotoConfiguration {
|
||||
maxEditVideoTime = second
|
||||
return self
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
func maxSelectVideoDuration(_ duration: Second) -> ZLPhotoConfiguration {
|
||||
maxSelectVideoDuration = duration
|
||||
return self
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
func minSelectVideoDuration(_ duration: Second) -> ZLPhotoConfiguration {
|
||||
minSelectVideoDuration = duration
|
||||
return self
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
func maxSelectVideoDataSize(_ size: ZLPhotoConfiguration.KBUnit) -> ZLPhotoConfiguration {
|
||||
maxSelectVideoDataSize = size
|
||||
return self
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
func minSelectVideoDataSize(_ size: ZLPhotoConfiguration.KBUnit) -> ZLPhotoConfiguration {
|
||||
minSelectVideoDataSize = size
|
||||
return self
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
func editImageConfiguration(_ configuration: ZLEditImageConfiguration) -> ZLPhotoConfiguration {
|
||||
editImageConfiguration = configuration
|
||||
return self
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
func showCaptureImageOnTakePhotoBtn(_ value: Bool) -> ZLPhotoConfiguration {
|
||||
showCaptureImageOnTakePhotoBtn = value
|
||||
return self
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
func showSelectBtnWhenSingleSelect(_ value: Bool) -> ZLPhotoConfiguration {
|
||||
showSelectBtnWhenSingleSelect = value
|
||||
return self
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
func showSelectedMask(_ value: Bool) -> ZLPhotoConfiguration {
|
||||
showSelectedMask = value
|
||||
return self
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
func showSelectedBorder(_ value: Bool) -> ZLPhotoConfiguration {
|
||||
showSelectedBorder = value
|
||||
return self
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
func showInvalidMask(_ value: Bool) -> ZLPhotoConfiguration {
|
||||
showInvalidMask = value
|
||||
return self
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
func showSelectedIndex(_ value: Bool) -> ZLPhotoConfiguration {
|
||||
showSelectedIndex = value
|
||||
return self
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
func showSelectedPhotoPreview(_ value: Bool) -> ZLPhotoConfiguration {
|
||||
showSelectedPhotoPreview = value
|
||||
return self
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
func timeout(_ timeout: TimeInterval) -> ZLPhotoConfiguration {
|
||||
self.timeout = timeout
|
||||
return self
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
func useCustomCamera(_ value: Bool) -> ZLPhotoConfiguration {
|
||||
useCustomCamera = value
|
||||
return self
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
func cameraConfiguration(_ configuration: ZLCameraConfiguration) -> ZLPhotoConfiguration {
|
||||
cameraConfiguration = configuration
|
||||
return self
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
func canSelectAsset(_ block: ((PHAsset) -> Bool)?) -> ZLPhotoConfiguration {
|
||||
canSelectAsset = block
|
||||
return self
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
func didSelectAsset(_ block: ((PHAsset) -> Void)?) -> ZLPhotoConfiguration {
|
||||
didSelectAsset = block
|
||||
return self
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
func didDeselectAsset(_ block: ((PHAsset) -> Void)?) -> ZLPhotoConfiguration {
|
||||
didDeselectAsset = block
|
||||
return self
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
func showAddPhotoButton(_ value: Bool) -> ZLPhotoConfiguration {
|
||||
showAddPhotoButton = value
|
||||
return self
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
func showEnterSettingTips(_ value: Bool) -> ZLPhotoConfiguration {
|
||||
showEnterSettingTips = value
|
||||
return self
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
func maxFrameCountForGIF(_ frameCount: Int) -> ZLPhotoConfiguration {
|
||||
maxFrameCountForGIF = frameCount
|
||||
return self
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
func gifPlayBlock(_ block: ((UIImageView, Data, [AnyHashable: Any]?) -> Void)?) -> ZLPhotoConfiguration {
|
||||
gifPlayBlock = block
|
||||
return self
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
func pauseGIFBlock(_ block: ((UIImageView) -> Void)?) -> ZLPhotoConfiguration {
|
||||
pauseGIFBlock = block
|
||||
return self
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
func resumeGIFBlock(_ block: ((UIImageView) -> Void)?) -> ZLPhotoConfiguration {
|
||||
resumeGIFBlock = block
|
||||
return self
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
func noAuthorityCallback(_ callback: ((ZLNoAuthorityType) -> Void)?) -> ZLPhotoConfiguration {
|
||||
noAuthorityCallback = callback
|
||||
return self
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
func operateBeforeDoneAction(_ block: ((UIViewController, @escaping () -> Void) -> Void)?) -> ZLPhotoConfiguration {
|
||||
operateBeforeDoneAction = block
|
||||
return self
|
||||
}
|
||||
}
|
||||
286
Pods/ZLPhotoBrowser/Sources/General/ZLPhotoConfiguration.swift
generated
Normal file
286
Pods/ZLPhotoBrowser/Sources/General/ZLPhotoConfiguration.swift
generated
Normal file
@@ -0,0 +1,286 @@
|
||||
//
|
||||
// ZLPhotoConfiguration.swift
|
||||
// ZLPhotoBrowser
|
||||
//
|
||||
// Created by long on 2020/8/11.
|
||||
//
|
||||
// Copyright (c) 2020 Long Zhang <495181165@qq.com>
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
// THE SOFTWARE.
|
||||
|
||||
import UIKit
|
||||
import Photos
|
||||
|
||||
@objcMembers
|
||||
public class ZLPhotoConfiguration: NSObject {
|
||||
public typealias Second = Int
|
||||
|
||||
public typealias KBUnit = CGFloat
|
||||
|
||||
private static var single = ZLPhotoConfiguration()
|
||||
|
||||
public class func `default`() -> ZLPhotoConfiguration {
|
||||
ZLPhotoConfiguration.single
|
||||
}
|
||||
|
||||
public class func resetConfiguration() {
|
||||
ZLPhotoConfiguration.single = ZLPhotoConfiguration()
|
||||
}
|
||||
|
||||
/// Photo sorting method, the preview interface is not affected by this parameter. Defaults to true.
|
||||
public var sortAscending = true
|
||||
|
||||
private var pri_maxSelectCount = 9
|
||||
/// Anything superior than 1 will enable the multiple selection feature. Defaults to 9.
|
||||
public var maxSelectCount: Int {
|
||||
get {
|
||||
pri_maxSelectCount
|
||||
}
|
||||
set {
|
||||
pri_maxSelectCount = max(1, newValue)
|
||||
}
|
||||
}
|
||||
|
||||
private var pri_maxVideoSelectCount = 0
|
||||
/// A count for video max selection. Defaults to 0.
|
||||
/// - warning: Only valid in mix selection mode. (i.e. allowMixSelect = true)
|
||||
public var maxVideoSelectCount: Int {
|
||||
get {
|
||||
if pri_maxVideoSelectCount <= 0 {
|
||||
return maxSelectCount
|
||||
} else {
|
||||
return max(minVideoSelectCount, min(pri_maxVideoSelectCount, maxSelectCount))
|
||||
}
|
||||
}
|
||||
set {
|
||||
pri_maxVideoSelectCount = newValue
|
||||
}
|
||||
}
|
||||
|
||||
private var pri_minVideoSelectCount = 0
|
||||
/// A count for video min selection. Defaults to 0.
|
||||
/// - warning: Only valid in mix selection mode. (i.e. allowMixSelect = true)
|
||||
public var minVideoSelectCount: Int {
|
||||
get {
|
||||
min(maxSelectCount, max(pri_minVideoSelectCount, 0))
|
||||
}
|
||||
set {
|
||||
pri_minVideoSelectCount = newValue
|
||||
}
|
||||
}
|
||||
|
||||
/// Whether photos and videos can be selected together. Defaults to true.
|
||||
/// If set to false, only one video can be selected. Defaults to true.
|
||||
public var allowMixSelect = true
|
||||
|
||||
/// Preview selection max preview count, if the value is zero, only show `Camera`, `Album`, `Cancel` buttons. Defaults to 20.
|
||||
public var maxPreviewCount = 20
|
||||
|
||||
/// If set to false, gif and livephoto cannot be selected either. Defaults to true.
|
||||
public var allowSelectImage = true
|
||||
|
||||
public var allowSelectVideo = true
|
||||
|
||||
/// Allow select Gif, it only controls whether it is displayed in Gif form.
|
||||
/// If value is false, the Gif logo is not displayed. Defaults to true.
|
||||
public var allowSelectGif = true
|
||||
|
||||
/// Allow select LivePhoto, it only controls whether it is displayed in LivePhoto form.
|
||||
/// If value is false, the LivePhoto logo is not displayed. Defaults to false.
|
||||
public var allowSelectLivePhoto = false
|
||||
|
||||
private var pri_allowTakePhotoInLibrary = true
|
||||
/// Allow take photos in the album. Defaults to true.
|
||||
/// - warning: If allowTakePhoto and allowRecordVideo are both false, it will not be displayed.
|
||||
public var allowTakePhotoInLibrary: Bool {
|
||||
get {
|
||||
pri_allowTakePhotoInLibrary && (cameraConfiguration.allowTakePhoto || cameraConfiguration.allowRecordVideo)
|
||||
}
|
||||
set {
|
||||
pri_allowTakePhotoInLibrary = newValue
|
||||
}
|
||||
}
|
||||
|
||||
/// Whether to callback directly after taking a photo. Defaults to false.
|
||||
public var callbackDirectlyAfterTakingPhoto = false
|
||||
|
||||
private var pri_allowEditImage = true
|
||||
public var allowEditImage: Bool {
|
||||
get {
|
||||
pri_allowEditImage
|
||||
}
|
||||
set {
|
||||
pri_allowEditImage = newValue
|
||||
}
|
||||
}
|
||||
|
||||
/// - warning: The video can only be edited when no photos are selected, or only one video is selected, and the selection callback is executed immediately after editing is completed.
|
||||
private var pri_allowEditVideo = false
|
||||
public var allowEditVideo: Bool {
|
||||
get {
|
||||
pri_allowEditVideo
|
||||
}
|
||||
set {
|
||||
pri_allowEditVideo = newValue
|
||||
}
|
||||
}
|
||||
|
||||
/// Control whether to display the selection button animation when selecting. Defaults to true.
|
||||
public var animateSelectBtnWhenSelect = true
|
||||
|
||||
/// Animation duration for select button
|
||||
public var selectBtnAnimationDuration: CFTimeInterval = 0.4
|
||||
|
||||
/// After selecting a image/video in the thumbnail interface, enter the editing interface directly. Defaults to false.
|
||||
/// - discussion: Editing image is only valid when allowEditImage is true and maxSelectCount is 1.
|
||||
/// Editing video is only valid when allowEditVideo is true and maxSelectCount is 1.
|
||||
public var editAfterSelectThumbnailImage = false
|
||||
|
||||
/// Only valid when allowMixSelect is false and allowEditVideo is true. Defaults to true.
|
||||
/// Just like the Wechat-Timeline selection style. If you want to crop the video after select thumbnail under allowMixSelect = true, please use **editAfterSelectThumbnailImage**.
|
||||
public var cropVideoAfterSelectThumbnail = true
|
||||
|
||||
/// If image edit tools only has clip and this property is true. When you click edit, the cropping interface (i.e. ZLClipImageViewController) will be displayed. Defaults to false.
|
||||
public var showClipDirectlyIfOnlyHasClipTool = false
|
||||
|
||||
/// Save the edited image to the album after editing. Defaults to true.
|
||||
public var saveNewImageAfterEdit = true
|
||||
|
||||
/// If true, you can slide select photos in album. Defaults to true.
|
||||
public var allowSlideSelect = true
|
||||
|
||||
/// When slide select is active, will auto scroll to top or bottom when your finger at the top or bottom. Defaults to true.
|
||||
public var autoScrollWhenSlideSelectIsActive = true
|
||||
|
||||
/// The max speed (pt/s) of auto scroll. Defaults to 600.
|
||||
public var autoScrollMaxSpeed: CGFloat = 600
|
||||
|
||||
/// If true, you can drag select photo when preview selection style. Defaults to false.
|
||||
public var allowDragSelect = false
|
||||
|
||||
/// Allow select full image. Defaults to true.
|
||||
public var allowSelectOriginal = true
|
||||
|
||||
/// Always return the original photo.
|
||||
/// - warning: Only valid when `allowSelectOriginal = false`, Defaults to false.
|
||||
public var alwaysRequestOriginal = false
|
||||
|
||||
/// Allow access to the preview large image interface (That is, whether to allow access to the large image interface after clicking the thumbnail image). Defaults to true.
|
||||
public var allowPreviewPhotos = true
|
||||
|
||||
/// Whether to show the preview button (i.e. the preview button in the lower left corner of the thumbnail interface). Defaults to true.
|
||||
public var showPreviewButtonInAlbum = true
|
||||
|
||||
/// Whether to display the selected count on the button. Defaults to true.
|
||||
public var showSelectCountOnDoneBtn = true
|
||||
|
||||
/// Maximum cropping time when editing video, unit: second. Defaults to 10.
|
||||
public var maxEditVideoTime: ZLPhotoConfiguration.Second = 10
|
||||
|
||||
/// Allow to choose the maximum duration of the video. Defaults to 120.
|
||||
public var maxSelectVideoDuration: ZLPhotoConfiguration.Second = 120
|
||||
|
||||
/// Allow to choose the minimum duration of the video. Defaults to 0.
|
||||
public var minSelectVideoDuration: ZLPhotoConfiguration.Second = 0
|
||||
|
||||
/// Allow to choose the maximum data size of the video. Defaults to infinite.
|
||||
public var maxSelectVideoDataSize: ZLPhotoConfiguration.KBUnit = .greatestFiniteMagnitude
|
||||
|
||||
/// Allow to choose the minimum data size of the video. Defaults to 0 KB.
|
||||
public var minSelectVideoDataSize: ZLPhotoConfiguration.KBUnit = 0
|
||||
|
||||
/// Image editor configuration.
|
||||
public var editImageConfiguration = ZLEditImageConfiguration()
|
||||
|
||||
/// Show the image captured by the camera is displayed on the camera button inside the album. Defaults to false.
|
||||
public var showCaptureImageOnTakePhotoBtn = false
|
||||
|
||||
/// In single selection mode, whether to display the selection button. Defaults to false.
|
||||
public var showSelectBtnWhenSingleSelect = false
|
||||
|
||||
/// Overlay a mask layer on top of the selected photos. Defaults to true.
|
||||
public var showSelectedMask = true
|
||||
|
||||
/// Display a border on the selected photos cell. Defaults to false.
|
||||
public var showSelectedBorder = false
|
||||
|
||||
/// Overlay a mask layer above the cells that cannot be selected. Defaults to true.
|
||||
public var showInvalidMask = true
|
||||
|
||||
/// Display the index of the selected photos. Defaults to true.
|
||||
public var showSelectedIndex = true
|
||||
|
||||
/// Display the selected photos at the bottom of the preview large photos interface. Defaults to true.
|
||||
public var showSelectedPhotoPreview = true
|
||||
|
||||
/// Timeout for image parsing. Defaults to 20.
|
||||
public var timeout: TimeInterval = 20
|
||||
|
||||
/// Whether to use custom camera. Defaults to true.
|
||||
public var useCustomCamera = true
|
||||
|
||||
/// The configuration for camera.
|
||||
public var cameraConfiguration = ZLCameraConfiguration()
|
||||
|
||||
/// This block will be called before selecting an image, the developer can first determine whether the asset is allowed to be selected.
|
||||
/// Only control whether it is allowed to be selected, and will not affect the selection logic in the framework.
|
||||
/// - Tips: If the choice is not allowed, the developer can toast prompt the user for relevant information.
|
||||
public var canSelectAsset: ((PHAsset) -> Bool)?
|
||||
|
||||
/// This block will be called when selecting an asset.
|
||||
public var didSelectAsset: ((PHAsset) -> Void)?
|
||||
|
||||
/// This block will be called when cancel selecting an asset.
|
||||
public var didDeselectAsset: ((PHAsset) -> Void)?
|
||||
|
||||
/// If user choose limited Photo mode, a button with '+' will be added to the ZLThumbnailViewController. It will call PHPhotoLibrary.shared().presentLimitedLibraryPicker(from:) to add photo. Defaults to true.
|
||||
/// E.g., Sina Weibo's ImagePicker
|
||||
public var showAddPhotoButton = true
|
||||
|
||||
/// iOS14 limited Photo mode, will show collection footer view in ZLThumbnailViewController.
|
||||
/// Will go to system setting if clicked. Defaults to true.
|
||||
public var showEnterSettingTips = true
|
||||
|
||||
/// The maximum number of frames for GIF images. To avoid crashes due to memory spikes caused by loading GIF images with too many frames, it is recommended that this value is not too large. Defaults to 50.
|
||||
public var maxFrameCountForGIF = 50
|
||||
|
||||
/// You can use this block to customize the playback of GIF images to achieve better results. For example, use FLAnimatedImage to play GIFs. Defaults to nil.
|
||||
public var gifPlayBlock: ((UIImageView, Data, [AnyHashable: Any]?) -> Void)?
|
||||
|
||||
/// Pause GIF image playback, used together with gifPlayBlock. Defaults to nil.
|
||||
public var pauseGIFBlock: ((UIImageView) -> Void)?
|
||||
|
||||
/// Resume GIF image playback, used together with gifPlayBlock. Defaults to nil.
|
||||
public var resumeGIFBlock: ((UIImageView) -> Void)?
|
||||
|
||||
/// Callback after the no authority alert dismiss.
|
||||
public var noAuthorityCallback: ((ZLNoAuthorityType) -> Void)?
|
||||
|
||||
/// Allow user to do something before select photo result callback.
|
||||
/// And you must call the second parameter of this block to continue the photos selection.
|
||||
/// The first parameter is the current controller.
|
||||
/// The second parameter is the block that needs to be called after the user completes the operation.
|
||||
public var operateBeforeDoneAction: ((UIViewController, @escaping () -> Void) -> Void)?
|
||||
}
|
||||
|
||||
@objc public enum ZLNoAuthorityType: Int {
|
||||
case library
|
||||
case camera
|
||||
case microphone
|
||||
}
|
||||
462
Pods/ZLPhotoBrowser/Sources/General/ZLPhotoManager.swift
generated
Normal file
462
Pods/ZLPhotoBrowser/Sources/General/ZLPhotoManager.swift
generated
Normal file
@@ -0,0 +1,462 @@
|
||||
//
|
||||
// ZLPhotoManager.swift
|
||||
// ZLPhotoBrowser
|
||||
//
|
||||
// Created by long on 2020/8/11.
|
||||
//
|
||||
// Copyright (c) 2020 Long Zhang <495181165@qq.com>
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
// THE SOFTWARE.
|
||||
|
||||
import UIKit
|
||||
import Photos
|
||||
|
||||
@objcMembers
|
||||
public class ZLPhotoManager: NSObject {
|
||||
/// Save image to album.
|
||||
public class func saveImageToAlbum(image: UIImage, completion: ((Bool, PHAsset?) -> Void)?) {
|
||||
let status = PHPhotoLibrary.authorizationStatus()
|
||||
|
||||
if status == .denied || status == .restricted {
|
||||
completion?(false, nil)
|
||||
return
|
||||
}
|
||||
var placeholderAsset: PHObjectPlaceholder?
|
||||
let completionHandler: ((Bool, Error?) -> Void) = { suc, _ in
|
||||
ZLMainAsync {
|
||||
if suc {
|
||||
let asset = self.getAsset(from: placeholderAsset?.localIdentifier)
|
||||
completion?(suc, asset)
|
||||
} else {
|
||||
completion?(false, nil)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if image.zl.hasAlphaChannel(), let data = image.pngData() {
|
||||
PHPhotoLibrary.shared().performChanges({
|
||||
let newAssetRequest = PHAssetCreationRequest.forAsset()
|
||||
newAssetRequest.addResource(with: .photo, data: data, options: nil)
|
||||
placeholderAsset = newAssetRequest.placeholderForCreatedAsset
|
||||
}, completionHandler: completionHandler)
|
||||
} else {
|
||||
PHPhotoLibrary.shared().performChanges({
|
||||
let newAssetRequest = PHAssetChangeRequest.creationRequestForAsset(from: image)
|
||||
placeholderAsset = newAssetRequest.placeholderForCreatedAsset
|
||||
}, completionHandler: completionHandler)
|
||||
}
|
||||
}
|
||||
|
||||
/// Save video to album.
|
||||
public class func saveVideoToAlbum(url: URL, completion: ((Bool, PHAsset?) -> Void)?) {
|
||||
let status = PHPhotoLibrary.authorizationStatus()
|
||||
|
||||
if status == .denied || status == .restricted {
|
||||
completion?(false, nil)
|
||||
return
|
||||
}
|
||||
|
||||
var placeholderAsset: PHObjectPlaceholder?
|
||||
PHPhotoLibrary.shared().performChanges({
|
||||
let newAssetRequest = PHAssetChangeRequest.creationRequestForAssetFromVideo(atFileURL: url)
|
||||
placeholderAsset = newAssetRequest?.placeholderForCreatedAsset
|
||||
}) { suc, _ in
|
||||
ZLMainAsync {
|
||||
if suc {
|
||||
let asset = self.getAsset(from: placeholderAsset?.localIdentifier)
|
||||
completion?(suc, asset)
|
||||
} else {
|
||||
completion?(false, nil)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class func getAsset(from localIdentifier: String?) -> PHAsset? {
|
||||
guard let id = localIdentifier else {
|
||||
return nil
|
||||
}
|
||||
|
||||
let result = PHAsset.fetchAssets(withLocalIdentifiers: [id], options: nil)
|
||||
return result.firstObject
|
||||
}
|
||||
|
||||
/// Fetch photos from result.
|
||||
public class func fetchPhoto(in result: PHFetchResult<PHAsset>, ascending: Bool, allowSelectImage: Bool, allowSelectVideo: Bool, limitCount: Int = .max) -> [ZLPhotoModel] {
|
||||
var models: [ZLPhotoModel] = []
|
||||
let option: NSEnumerationOptions = ascending ? .init(rawValue: 0) : .reverse
|
||||
var count = 1
|
||||
|
||||
result.enumerateObjects(options: option) { asset, _, stop in
|
||||
let m = ZLPhotoModel(asset: asset)
|
||||
|
||||
if m.type == .image, !allowSelectImage {
|
||||
return
|
||||
}
|
||||
if m.type == .video, !allowSelectVideo {
|
||||
return
|
||||
}
|
||||
if count == limitCount {
|
||||
stop.pointee = true
|
||||
}
|
||||
|
||||
models.append(m)
|
||||
count += 1
|
||||
}
|
||||
|
||||
return models
|
||||
}
|
||||
|
||||
/// Fetch all album list.
|
||||
public class func getPhotoAlbumList(ascending: Bool, allowSelectImage: Bool, allowSelectVideo: Bool, completion: ([ZLAlbumListModel]) -> Void) {
|
||||
let option = PHFetchOptions()
|
||||
if !allowSelectImage {
|
||||
option.predicate = NSPredicate(format: "mediaType == %ld", PHAssetMediaType.video.rawValue)
|
||||
}
|
||||
if !allowSelectVideo {
|
||||
option.predicate = NSPredicate(format: "mediaType == %ld", PHAssetMediaType.image.rawValue)
|
||||
}
|
||||
|
||||
let smartAlbums = PHAssetCollection.fetchAssetCollections(with: .smartAlbum, subtype: .albumRegular, options: nil) as! PHFetchResult<PHCollection>
|
||||
let albums = PHAssetCollection.fetchAssetCollections(with: .album, subtype: .albumRegular, options: nil) as! PHFetchResult<PHCollection>
|
||||
let streamAlbums = PHAssetCollection.fetchAssetCollections(with: .album, subtype: .albumMyPhotoStream, options: nil) as! PHFetchResult<PHCollection>
|
||||
let syncedAlbums = PHAssetCollection.fetchAssetCollections(with: .album, subtype: .albumSyncedAlbum, options: nil) as! PHFetchResult<PHCollection>
|
||||
let sharedAlbums = PHAssetCollection.fetchAssetCollections(with: .album, subtype: .albumCloudShared, options: nil) as! PHFetchResult<PHCollection>
|
||||
let arr = [smartAlbums, albums, streamAlbums, syncedAlbums, sharedAlbums]
|
||||
|
||||
var albumList: [ZLAlbumListModel] = []
|
||||
arr.forEach { album in
|
||||
album.enumerateObjects { collection, _, _ in
|
||||
guard let collection = collection as? PHAssetCollection else { return }
|
||||
if collection.assetCollectionSubtype == .smartAlbumAllHidden {
|
||||
return
|
||||
}
|
||||
if #available(iOS 11.0, *), collection.assetCollectionSubtype.rawValue > PHAssetCollectionSubtype.smartAlbumLongExposures.rawValue {
|
||||
return
|
||||
}
|
||||
let result = PHAsset.fetchAssets(in: collection, options: option)
|
||||
if result.count == 0 {
|
||||
return
|
||||
}
|
||||
let title = self.getCollectionTitle(collection)
|
||||
|
||||
if collection.assetCollectionSubtype == .smartAlbumUserLibrary {
|
||||
// Album of all photos.
|
||||
let m = ZLAlbumListModel(title: title, result: result, collection: collection, option: option, isCameraRoll: true)
|
||||
albumList.insert(m, at: 0)
|
||||
} else {
|
||||
let m = ZLAlbumListModel(title: title, result: result, collection: collection, option: option, isCameraRoll: false)
|
||||
albumList.append(m)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
completion(albumList)
|
||||
}
|
||||
|
||||
/// Fetch camera roll album.
|
||||
public class func getCameraRollAlbum(allowSelectImage: Bool, allowSelectVideo: Bool, completion: @escaping (ZLAlbumListModel) -> Void) {
|
||||
DispatchQueue.global().async {
|
||||
let option = PHFetchOptions()
|
||||
if !allowSelectImage {
|
||||
option.predicate = NSPredicate(format: "mediaType == %ld", PHAssetMediaType.video.rawValue)
|
||||
}
|
||||
if !allowSelectVideo {
|
||||
option.predicate = NSPredicate(format: "mediaType == %ld", PHAssetMediaType.image.rawValue)
|
||||
}
|
||||
|
||||
let smartAlbums = PHAssetCollection.fetchAssetCollections(with: .smartAlbum, subtype: .albumRegular, options: nil)
|
||||
smartAlbums.enumerateObjects { collection, _, stop in
|
||||
if collection.assetCollectionSubtype == .smartAlbumUserLibrary {
|
||||
stop.pointee = true
|
||||
|
||||
let result = PHAsset.fetchAssets(in: collection, options: option)
|
||||
let albumModel = ZLAlbumListModel(title: self.getCollectionTitle(collection), result: result, collection: collection, option: option, isCameraRoll: true)
|
||||
ZLMainAsync {
|
||||
completion(albumModel)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Conversion collection title.
|
||||
private class func getCollectionTitle(_ collection: PHAssetCollection) -> String {
|
||||
if collection.assetCollectionType == .album {
|
||||
// Albums created by user.
|
||||
var title: String?
|
||||
if ZLCustomLanguageDeploy.language == .system {
|
||||
title = collection.localizedTitle
|
||||
} else {
|
||||
switch collection.assetCollectionSubtype {
|
||||
case .albumMyPhotoStream:
|
||||
title = localLanguageTextValue(.myPhotoStream)
|
||||
default:
|
||||
title = collection.localizedTitle
|
||||
}
|
||||
}
|
||||
return title ?? localLanguageTextValue(.noTitleAlbumListPlaceholder)
|
||||
}
|
||||
|
||||
var title: String?
|
||||
if ZLCustomLanguageDeploy.language == .system {
|
||||
title = collection.localizedTitle
|
||||
} else {
|
||||
switch collection.assetCollectionSubtype {
|
||||
case .smartAlbumUserLibrary:
|
||||
title = localLanguageTextValue(.cameraRoll)
|
||||
case .smartAlbumPanoramas:
|
||||
title = localLanguageTextValue(.panoramas)
|
||||
case .smartAlbumVideos:
|
||||
title = localLanguageTextValue(.videos)
|
||||
case .smartAlbumFavorites:
|
||||
title = localLanguageTextValue(.favorites)
|
||||
case .smartAlbumTimelapses:
|
||||
title = localLanguageTextValue(.timelapses)
|
||||
case .smartAlbumRecentlyAdded:
|
||||
title = localLanguageTextValue(.recentlyAdded)
|
||||
case .smartAlbumBursts:
|
||||
title = localLanguageTextValue(.bursts)
|
||||
case .smartAlbumSlomoVideos:
|
||||
title = localLanguageTextValue(.slomoVideos)
|
||||
case .smartAlbumSelfPortraits:
|
||||
title = localLanguageTextValue(.selfPortraits)
|
||||
case .smartAlbumScreenshots:
|
||||
title = localLanguageTextValue(.screenshots)
|
||||
case .smartAlbumDepthEffect:
|
||||
title = localLanguageTextValue(.depthEffect)
|
||||
case .smartAlbumLivePhotos:
|
||||
title = localLanguageTextValue(.livePhotos)
|
||||
default:
|
||||
title = collection.localizedTitle
|
||||
}
|
||||
|
||||
if #available(iOS 11.0, *) {
|
||||
if collection.assetCollectionSubtype == PHAssetCollectionSubtype.smartAlbumAnimated {
|
||||
title = localLanguageTextValue(.animated)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return title ?? localLanguageTextValue(.noTitleAlbumListPlaceholder)
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
public class func fetchImage(for asset: PHAsset, size: CGSize, progress: ((CGFloat, Error?, UnsafeMutablePointer<ObjCBool>, [AnyHashable: Any]?) -> Void)? = nil, completion: @escaping (UIImage?, Bool) -> Void) -> PHImageRequestID {
|
||||
return fetchImage(for: asset, size: size, resizeMode: .fast, progress: progress, completion: completion)
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
public class func fetchOriginalImage(for asset: PHAsset, progress: ((CGFloat, Error?, UnsafeMutablePointer<ObjCBool>, [AnyHashable: Any]?) -> Void)? = nil, completion: @escaping (UIImage?, Bool) -> Void) -> PHImageRequestID {
|
||||
return fetchImage(for: asset, size: PHImageManagerMaximumSize, resizeMode: .fast, progress: progress, completion: completion)
|
||||
}
|
||||
|
||||
/// Fetch asset data.
|
||||
@discardableResult
|
||||
public class func fetchOriginalImageData(for asset: PHAsset, progress: ((CGFloat, Error?, UnsafeMutablePointer<ObjCBool>, [AnyHashable: Any]?) -> Void)? = nil, completion: @escaping (Data, [AnyHashable: Any]?, Bool) -> Void) -> PHImageRequestID {
|
||||
let option = PHImageRequestOptions()
|
||||
if asset.zl.isGif {
|
||||
option.version = .original
|
||||
}
|
||||
option.isNetworkAccessAllowed = true
|
||||
option.resizeMode = .fast
|
||||
option.deliveryMode = .highQualityFormat
|
||||
option.progressHandler = { pro, error, stop, info in
|
||||
ZLMainAsync {
|
||||
progress?(CGFloat(pro), error, stop, info)
|
||||
}
|
||||
}
|
||||
|
||||
return PHImageManager.default().requestImageData(for: asset, options: option) { data, _, _, info in
|
||||
let cancel = info?[PHImageCancelledKey] as? Bool ?? false
|
||||
let isDegraded = (info?[PHImageResultIsDegradedKey] as? Bool ?? false)
|
||||
if !cancel, let data = data {
|
||||
completion(data, info, isDegraded)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Fetch image for asset.
|
||||
private class func fetchImage(for asset: PHAsset, size: CGSize, resizeMode: PHImageRequestOptionsResizeMode, progress: ((CGFloat, Error?, UnsafeMutablePointer<ObjCBool>, [AnyHashable: Any]?) -> Void)? = nil, completion: @escaping (UIImage?, Bool) -> Void) -> PHImageRequestID {
|
||||
let option = PHImageRequestOptions()
|
||||
option.resizeMode = resizeMode
|
||||
option.isNetworkAccessAllowed = true
|
||||
option.progressHandler = { pro, error, stop, info in
|
||||
ZLMainAsync {
|
||||
progress?(CGFloat(pro), error, stop, info)
|
||||
}
|
||||
}
|
||||
|
||||
return PHImageManager.default().requestImage(for: asset, targetSize: size, contentMode: .aspectFill, options: option) { image, info in
|
||||
var downloadFinished = false
|
||||
if let info = info {
|
||||
downloadFinished = !(info[PHImageCancelledKey] as? Bool ?? false) && (info[PHImageErrorKey] == nil)
|
||||
}
|
||||
let isDegraded = (info?[PHImageResultIsDegradedKey] as? Bool ?? false)
|
||||
if downloadFinished {
|
||||
ZLMainAsync {
|
||||
completion(image, isDegraded)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class func fetchLivePhoto(for asset: PHAsset, completion: @escaping (PHLivePhoto?, [AnyHashable: Any]?, Bool) -> Void) -> PHImageRequestID {
|
||||
let option = PHLivePhotoRequestOptions()
|
||||
option.version = .current
|
||||
option.deliveryMode = .opportunistic
|
||||
option.isNetworkAccessAllowed = true
|
||||
|
||||
return PHImageManager.default().requestLivePhoto(for: asset, targetSize: PHImageManagerMaximumSize, contentMode: .aspectFit, options: option) { livePhoto, info in
|
||||
let isDegraded = (info?[PHImageResultIsDegradedKey] as? Bool ?? false)
|
||||
completion(livePhoto, info, isDegraded)
|
||||
}
|
||||
}
|
||||
|
||||
public class func fetchVideo(for asset: PHAsset, progress: ((CGFloat, Error?, UnsafeMutablePointer<ObjCBool>, [AnyHashable: Any]?) -> Void)? = nil, completion: @escaping (AVPlayerItem?, [AnyHashable: Any]?, Bool) -> Void) -> PHImageRequestID {
|
||||
let option = PHVideoRequestOptions()
|
||||
option.isNetworkAccessAllowed = true
|
||||
option.progressHandler = { pro, error, stop, info in
|
||||
ZLMainAsync {
|
||||
progress?(CGFloat(pro), error, stop, info)
|
||||
}
|
||||
}
|
||||
|
||||
// https://github.com/longitachi/ZLPhotoBrowser/issues/369#issuecomment-728679135
|
||||
if asset.zl.isInCloud {
|
||||
return PHImageManager.default().requestExportSession(forVideo: asset, options: option, exportPreset: AVAssetExportPresetHighestQuality, resultHandler: { session, info in
|
||||
// iOS11 and earlier, callback is not on the main thread.
|
||||
ZLMainAsync {
|
||||
let isDegraded = (info?[PHImageResultIsDegradedKey] as? Bool ?? false)
|
||||
if let avAsset = session?.asset {
|
||||
let item = AVPlayerItem(asset: avAsset)
|
||||
completion(item, info, isDegraded)
|
||||
} else {
|
||||
completion(nil, nil, true)
|
||||
}
|
||||
}
|
||||
})
|
||||
} else {
|
||||
return PHImageManager.default().requestPlayerItem(forVideo: asset, options: option) { item, info in
|
||||
// iOS11 and earlier, callback is not on the main thread.
|
||||
ZLMainAsync {
|
||||
let isDegraded = (info?[PHImageResultIsDegradedKey] as? Bool ?? false)
|
||||
completion(item, info, isDegraded)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class func isFetchImageError(_ error: Error?) -> Bool {
|
||||
guard let e = error as NSError? else {
|
||||
return false
|
||||
}
|
||||
if e.domain == "CKErrorDomain" || e.domain == "CloudPhotoLibraryErrorDomain" {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
public class func fetchAVAsset(forVideo asset: PHAsset, completion: @escaping (AVAsset?, [AnyHashable: Any]?) -> Void) -> PHImageRequestID {
|
||||
let options = PHVideoRequestOptions()
|
||||
options.deliveryMode = .automatic
|
||||
options.isNetworkAccessAllowed = true
|
||||
|
||||
if asset.zl.isInCloud {
|
||||
return PHImageManager.default().requestExportSession(forVideo: asset, options: options, exportPreset: AVAssetExportPresetHighestQuality) { session, info in
|
||||
// iOS11 and earlier, callback is not on the main thread.
|
||||
ZLMainAsync {
|
||||
if let avAsset = session?.asset {
|
||||
completion(avAsset, info)
|
||||
} else {
|
||||
completion(nil, info)
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return PHImageManager.default().requestAVAsset(forVideo: asset, options: options) { avAsset, _, info in
|
||||
ZLMainAsync {
|
||||
completion(avAsset, info)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Fetch the size of asset. Unit is KB.
|
||||
public class func fetchAssetSize(for asset: PHAsset) -> ZLPhotoConfiguration.KBUnit? {
|
||||
guard let resource = PHAssetResource.assetResources(for: asset).first,
|
||||
let size = resource.value(forKey: "fileSize") as? CGFloat else {
|
||||
return nil
|
||||
}
|
||||
|
||||
return size / 1024
|
||||
}
|
||||
|
||||
/// Fetch asset local file path.
|
||||
/// - Note: Asynchronously to fetch the file path. calls completionHandler block on the main queue.
|
||||
public class func fetchAssetFilePath(for asset: PHAsset, completion: @escaping (String?) -> Void) {
|
||||
asset.requestContentEditingInput(with: nil) { input, _ in
|
||||
var path = input?.fullSizeImageURL?.absoluteString
|
||||
if path == nil,
|
||||
let dir = asset.value(forKey: "directory") as? String,
|
||||
let name = asset.zl.filename {
|
||||
path = String(format: "file:///var/mobile/Media/%@/%@", dir, name)
|
||||
}
|
||||
completion(path)
|
||||
}
|
||||
}
|
||||
|
||||
/// Save asset original data to file url. Support save image and video.
|
||||
/// - Note: Asynchronously write to a local file. Calls completionHandler block on the main queue.
|
||||
public class func saveAsset(_ asset: PHAsset, toFile fileUrl: URL, completion: @escaping ((Error?) -> Void)) {
|
||||
guard let resource = asset.zl.resource else {
|
||||
completion(NSError.assetSaveError)
|
||||
return
|
||||
}
|
||||
|
||||
PHAssetResourceManager.default().writeData(for: resource, toFile: fileUrl, options: nil) { error in
|
||||
ZLMainAsync {
|
||||
completion(error)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Authority related.
|
||||
public extension ZLPhotoManager {
|
||||
class func hasPhotoLibratyAuthority() -> Bool {
|
||||
return PHPhotoLibrary.authorizationStatus() == .authorized
|
||||
}
|
||||
|
||||
class func hasCameraAuthority() -> Bool {
|
||||
let status = AVCaptureDevice.authorizationStatus(for: .video)
|
||||
if status == .restricted || status == .denied {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
class func hasMicrophoneAuthority() -> Bool {
|
||||
let status = AVCaptureDevice.authorizationStatus(for: .audio)
|
||||
if status == .restricted || status == .denied {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
165
Pods/ZLPhotoBrowser/Sources/General/ZLPhotoModel.swift
generated
Normal file
165
Pods/ZLPhotoBrowser/Sources/General/ZLPhotoModel.swift
generated
Normal file
@@ -0,0 +1,165 @@
|
||||
//
|
||||
// ZLPhotoModel.swift
|
||||
// ZLPhotoBrowser
|
||||
//
|
||||
// Created by long on 2020/8/11.
|
||||
//
|
||||
// Copyright (c) 2020 Long Zhang <495181165@qq.com>
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
// THE SOFTWARE.
|
||||
|
||||
import UIKit
|
||||
import Photos
|
||||
|
||||
public extension ZLPhotoModel {
|
||||
|
||||
enum MediaType: Int {
|
||||
case unknown = 0
|
||||
case image
|
||||
case gif
|
||||
case livePhoto
|
||||
case video
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public class ZLPhotoModel: NSObject {
|
||||
|
||||
public let ident: String
|
||||
|
||||
public let asset: PHAsset
|
||||
|
||||
public var type: ZLPhotoModel.MediaType = .unknown
|
||||
|
||||
public var duration: String = ""
|
||||
|
||||
public var isSelected: Bool = false
|
||||
|
||||
private var pri_dataSize: ZLPhotoConfiguration.KBUnit?
|
||||
|
||||
public var dataSize: ZLPhotoConfiguration.KBUnit? {
|
||||
if let pri_dataSize = pri_dataSize {
|
||||
return pri_dataSize
|
||||
}
|
||||
|
||||
let size = ZLPhotoManager.fetchAssetSize(for: asset)
|
||||
pri_dataSize = size
|
||||
|
||||
return size
|
||||
}
|
||||
|
||||
private var pri_editImage: UIImage?
|
||||
|
||||
public var editImage: UIImage? {
|
||||
set {
|
||||
pri_editImage = newValue
|
||||
}
|
||||
get {
|
||||
if let _ = editImageModel {
|
||||
return pri_editImage
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public var second: ZLPhotoConfiguration.Second {
|
||||
guard type == .video else {
|
||||
return 0
|
||||
}
|
||||
return Int(round(asset.duration))
|
||||
}
|
||||
|
||||
public var whRatio: CGFloat {
|
||||
return CGFloat(asset.pixelWidth) / CGFloat(asset.pixelHeight)
|
||||
}
|
||||
|
||||
public var previewSize: CGSize {
|
||||
let scale: CGFloat = UIScreen.main.scale
|
||||
if whRatio > 1 {
|
||||
let h = min(UIScreen.main.bounds.height, ZLMaxImageWidth) * scale
|
||||
let w = h * whRatio
|
||||
return CGSize(width: w, height: h)
|
||||
} else {
|
||||
let w = min(UIScreen.main.bounds.width, ZLMaxImageWidth) * scale
|
||||
let h = w / whRatio
|
||||
return CGSize(width: w, height: h)
|
||||
}
|
||||
}
|
||||
|
||||
// Content of the last edit.
|
||||
public var editImageModel: ZLEditImageModel?
|
||||
|
||||
public init(asset: PHAsset) {
|
||||
ident = asset.localIdentifier
|
||||
self.asset = asset
|
||||
super.init()
|
||||
|
||||
type = transformAssetType(for: asset)
|
||||
if type == .video {
|
||||
duration = transformDuration(for: asset)
|
||||
}
|
||||
}
|
||||
|
||||
public func transformAssetType(for asset: PHAsset) -> ZLPhotoModel.MediaType {
|
||||
switch asset.mediaType {
|
||||
case .video:
|
||||
return .video
|
||||
case .image:
|
||||
if asset.zl.isGif {
|
||||
return .gif
|
||||
}
|
||||
if asset.mediaSubtypes.contains(.photoLive) {
|
||||
return .livePhoto
|
||||
}
|
||||
return .image
|
||||
default:
|
||||
return .unknown
|
||||
}
|
||||
}
|
||||
|
||||
public func transformDuration(for asset: PHAsset) -> String {
|
||||
let dur = Int(round(asset.duration))
|
||||
|
||||
switch dur {
|
||||
case 0..<60:
|
||||
return String(format: "00:%02d", dur)
|
||||
case 60..<3600:
|
||||
let m = dur / 60
|
||||
let s = dur % 60
|
||||
return String(format: "%02d:%02d", m, s)
|
||||
case 3600...:
|
||||
let h = dur / 3600
|
||||
let m = (dur % 3600) / 60
|
||||
let s = dur % 60
|
||||
return String(format: "%02d:%02d:%02d", h, m, s)
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public extension ZLPhotoModel {
|
||||
|
||||
static func ==(lhs: ZLPhotoModel, rhs: ZLPhotoModel) -> Bool {
|
||||
return lhs.ident == rhs.ident
|
||||
}
|
||||
|
||||
}
|
||||
1189
Pods/ZLPhotoBrowser/Sources/General/ZLPhotoPreviewCell.swift
generated
Normal file
1189
Pods/ZLPhotoBrowser/Sources/General/ZLPhotoPreviewCell.swift
generated
Normal file
File diff suppressed because it is too large
Load Diff
1172
Pods/ZLPhotoBrowser/Sources/General/ZLPhotoPreviewController.swift
generated
Normal file
1172
Pods/ZLPhotoBrowser/Sources/General/ZLPhotoPreviewController.swift
generated
Normal file
File diff suppressed because it is too large
Load Diff
1139
Pods/ZLPhotoBrowser/Sources/General/ZLPhotoPreviewSheet.swift
generated
Normal file
1139
Pods/ZLPhotoBrowser/Sources/General/ZLPhotoPreviewSheet.swift
generated
Normal file
File diff suppressed because it is too large
Load Diff
434
Pods/ZLPhotoBrowser/Sources/General/ZLPhotoUIConfiguration+Chaining.swift
generated
Normal file
434
Pods/ZLPhotoBrowser/Sources/General/ZLPhotoUIConfiguration+Chaining.swift
generated
Normal file
@@ -0,0 +1,434 @@
|
||||
//
|
||||
// ZLPhotoUIConfiguration+Chaining.swift
|
||||
// ZLPhotoBrowser
|
||||
//
|
||||
// Created by long on 2022/4/19.
|
||||
//
|
||||
// Copyright (c) 2020 Long Zhang <495181165@qq.com>
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
// THE SOFTWARE.
|
||||
|
||||
import UIKit
|
||||
|
||||
// MARK: chaining
|
||||
|
||||
public extension ZLPhotoUIConfiguration {
|
||||
@discardableResult
|
||||
func style(_ style: ZLPhotoBrowserStyle) -> ZLPhotoUIConfiguration {
|
||||
self.style = style
|
||||
return self
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
func statusBarStyle(_ statusBarStyle: UIStatusBarStyle) -> ZLPhotoUIConfiguration {
|
||||
self.statusBarStyle = statusBarStyle
|
||||
return self
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
func navCancelButtonStyle(_ style: ZLPhotoUIConfiguration.CancelButtonStyle) -> ZLPhotoUIConfiguration {
|
||||
navCancelButtonStyle = style
|
||||
return self
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
func showStatusBarInPreviewInterface(_ value: Bool) -> ZLPhotoUIConfiguration {
|
||||
showStatusBarInPreviewInterface = value
|
||||
return self
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
func hudStyle(_ style: ZLProgressHUD.HUDStyle) -> ZLPhotoUIConfiguration {
|
||||
hudStyle = style
|
||||
return self
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
func adjustSliderType(_ type: ZLAdjustSliderType) -> ZLPhotoUIConfiguration {
|
||||
adjustSliderType = type
|
||||
return self
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
func cellCornerRadio(_ cornerRadio: CGFloat) -> ZLPhotoUIConfiguration {
|
||||
cellCornerRadio = cornerRadio
|
||||
return self
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
func customAlertClass(_ alertClass: ZLCustomAlertProtocol.Type?) -> ZLPhotoUIConfiguration {
|
||||
customAlertClass = alertClass
|
||||
return self
|
||||
}
|
||||
|
||||
/// - Note: This property is ignored when using columnCountBlock.
|
||||
@discardableResult
|
||||
func columnCount(_ count: Int) -> ZLPhotoUIConfiguration {
|
||||
columnCount = count
|
||||
return self
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
func columnCountBlock(_ block: ((_ collectionViewWidth: CGFloat) -> Int)?) -> ZLPhotoUIConfiguration {
|
||||
columnCountBlock = block
|
||||
return self
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
func minimumInteritemSpacing(_ value: CGFloat) -> ZLPhotoUIConfiguration {
|
||||
minimumInteritemSpacing = value
|
||||
return self
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
func minimumLineSpacing(_ value: CGFloat) -> ZLPhotoUIConfiguration {
|
||||
minimumLineSpacing = value
|
||||
return self
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
func navViewBlurEffectOfAlbumList(_ effect: UIBlurEffect?) -> ZLPhotoUIConfiguration {
|
||||
navViewBlurEffectOfAlbumList = effect
|
||||
return self
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
func navViewBlurEffectOfPreview(_ effect: UIBlurEffect?) -> ZLPhotoUIConfiguration {
|
||||
navViewBlurEffectOfPreview = effect
|
||||
return self
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
func bottomViewBlurEffectOfAlbumList(_ effect: UIBlurEffect?) -> ZLPhotoUIConfiguration {
|
||||
bottomViewBlurEffectOfAlbumList = effect
|
||||
return self
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
func bottomViewBlurEffectOfPreview(_ effect: UIBlurEffect?) -> ZLPhotoUIConfiguration {
|
||||
bottomViewBlurEffectOfPreview = effect
|
||||
return self
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
func customImageNames(_ names: [String]) -> ZLPhotoUIConfiguration {
|
||||
customImageNames = names
|
||||
return self
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
func customImageForKey(_ map: [String: UIImage?]) -> ZLPhotoUIConfiguration {
|
||||
customImageForKey = map
|
||||
return self
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
func languageType(_ type: ZLLanguageType) -> ZLPhotoUIConfiguration {
|
||||
languageType = type
|
||||
return self
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
func customLanguageKeyValue(_ map: [ZLLocalLanguageKey: String]) -> ZLPhotoUIConfiguration {
|
||||
customLanguageKeyValue = map
|
||||
return self
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
func themeFontName(_ name: String) -> ZLPhotoUIConfiguration {
|
||||
themeFontName = name
|
||||
return self
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
func themeColor(_ color: UIColor) -> ZLPhotoUIConfiguration {
|
||||
themeColor = color
|
||||
return self
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
func sheetTranslucentColor(_ color: UIColor) -> ZLPhotoUIConfiguration {
|
||||
sheetTranslucentColor = color
|
||||
return self
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
func sheetBtnBgColor(_ color: UIColor) -> ZLPhotoUIConfiguration {
|
||||
sheetBtnBgColor = color
|
||||
return self
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
func sheetBtnTitleColor(_ color: UIColor) -> ZLPhotoUIConfiguration {
|
||||
sheetBtnTitleColor = color
|
||||
return self
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
func sheetBtnTitleTintColor(_ color: UIColor) -> ZLPhotoUIConfiguration {
|
||||
sheetBtnTitleTintColor = color
|
||||
return self
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
func navBarColor(_ color: UIColor) -> ZLPhotoUIConfiguration {
|
||||
navBarColor = color
|
||||
return self
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
func navBarColorOfPreviewVC(_ color: UIColor) -> ZLPhotoUIConfiguration {
|
||||
navBarColorOfPreviewVC = color
|
||||
return self
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
func navTitleColor(_ color: UIColor) -> ZLPhotoUIConfiguration {
|
||||
navTitleColor = color
|
||||
return self
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
func navTitleColorOfPreviewVC(_ color: UIColor) -> ZLPhotoUIConfiguration {
|
||||
navTitleColorOfPreviewVC = color
|
||||
return self
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
func navEmbedTitleViewBgColor(_ color: UIColor) -> ZLPhotoUIConfiguration {
|
||||
navEmbedTitleViewBgColor = color
|
||||
return self
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
func albumListBgColor(_ color: UIColor) -> ZLPhotoUIConfiguration {
|
||||
albumListBgColor = color
|
||||
return self
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
func embedAlbumListTranslucentColor(_ color: UIColor) -> ZLPhotoUIConfiguration {
|
||||
embedAlbumListTranslucentColor = color
|
||||
return self
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
func albumListTitleColor(_ color: UIColor) -> ZLPhotoUIConfiguration {
|
||||
albumListTitleColor = color
|
||||
return self
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
func albumListCountColor(_ color: UIColor) -> ZLPhotoUIConfiguration {
|
||||
albumListCountColor = color
|
||||
return self
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
func separatorColor(_ color: UIColor) -> ZLPhotoUIConfiguration {
|
||||
separatorColor = color
|
||||
return self
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
func thumbnailBgColor(_ color: UIColor) -> ZLPhotoUIConfiguration {
|
||||
thumbnailBgColor = color
|
||||
return self
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
func previewVCBgColor(_ color: UIColor) -> ZLPhotoUIConfiguration {
|
||||
previewVCBgColor = color
|
||||
return self
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
func bottomToolViewBgColor(_ color: UIColor) -> ZLPhotoUIConfiguration {
|
||||
bottomToolViewBgColor = color
|
||||
return self
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
func bottomToolViewBgColorOfPreviewVC(_ color: UIColor) -> ZLPhotoUIConfiguration {
|
||||
bottomToolViewBgColorOfPreviewVC = color
|
||||
return self
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
func bottomToolViewBtnNormalTitleColor(_ color: UIColor) -> ZLPhotoUIConfiguration {
|
||||
bottomToolViewBtnNormalTitleColor = color
|
||||
return self
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
func bottomToolViewDoneBtnNormalTitleColor(_ color: UIColor) -> ZLPhotoUIConfiguration {
|
||||
bottomToolViewDoneBtnNormalTitleColor = color
|
||||
return self
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
func bottomToolViewBtnNormalTitleColorOfPreviewVC(_ color: UIColor) -> ZLPhotoUIConfiguration {
|
||||
bottomToolViewBtnNormalTitleColorOfPreviewVC = color
|
||||
return self
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
func bottomToolViewDoneBtnNormalTitleColorOfPreviewVC(_ color: UIColor) -> ZLPhotoUIConfiguration {
|
||||
bottomToolViewDoneBtnNormalTitleColorOfPreviewVC = color
|
||||
return self
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
func bottomToolViewBtnDisableTitleColor(_ color: UIColor) -> ZLPhotoUIConfiguration {
|
||||
bottomToolViewBtnDisableTitleColor = color
|
||||
return self
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
func bottomToolViewDoneBtnDisableTitleColor(_ color: UIColor) -> ZLPhotoUIConfiguration {
|
||||
bottomToolViewDoneBtnDisableTitleColor = color
|
||||
return self
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
func bottomToolViewBtnDisableTitleColorOfPreviewVC(_ color: UIColor) -> ZLPhotoUIConfiguration {
|
||||
bottomToolViewBtnDisableTitleColorOfPreviewVC = color
|
||||
return self
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
func bottomToolViewDoneBtnDisableTitleColorOfPreviewVC(_ color: UIColor) -> ZLPhotoUIConfiguration {
|
||||
bottomToolViewDoneBtnDisableTitleColorOfPreviewVC = color
|
||||
return self
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
func bottomToolViewBtnNormalBgColor(_ color: UIColor) -> ZLPhotoUIConfiguration {
|
||||
bottomToolViewBtnNormalBgColor = color
|
||||
return self
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
func bottomToolViewBtnNormalBgColorOfPreviewVC(_ color: UIColor) -> ZLPhotoUIConfiguration {
|
||||
bottomToolViewBtnNormalBgColorOfPreviewVC = color
|
||||
return self
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
func bottomToolViewBtnDisableBgColor(_ color: UIColor) -> ZLPhotoUIConfiguration {
|
||||
bottomToolViewBtnDisableBgColor = color
|
||||
return self
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
func bottomToolViewBtnDisableBgColorOfPreviewVC(_ color: UIColor) -> ZLPhotoUIConfiguration {
|
||||
bottomToolViewBtnDisableBgColorOfPreviewVC = color
|
||||
return self
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
func limitedAuthorityTipsColor(_ color: UIColor) -> ZLPhotoUIConfiguration {
|
||||
limitedAuthorityTipsColor = color
|
||||
return self
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
func cameraRecodeProgressColor(_ color: UIColor) -> ZLPhotoUIConfiguration {
|
||||
cameraRecodeProgressColor = color
|
||||
return self
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
func selectedMaskColor(_ color: UIColor) -> ZLPhotoUIConfiguration {
|
||||
selectedMaskColor = color
|
||||
return self
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
func selectedBorderColor(_ color: UIColor) -> ZLPhotoUIConfiguration {
|
||||
selectedBorderColor = color
|
||||
return self
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
func invalidMaskColor(_ color: UIColor) -> ZLPhotoUIConfiguration {
|
||||
invalidMaskColor = color
|
||||
return self
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
func indexLabelTextColor(_ color: UIColor) -> ZLPhotoUIConfiguration {
|
||||
indexLabelTextColor = color
|
||||
return self
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
func indexLabelBgColor(_ color: UIColor) -> ZLPhotoUIConfiguration {
|
||||
indexLabelBgColor = color
|
||||
return self
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
func cameraCellBgColor(_ color: UIColor) -> ZLPhotoUIConfiguration {
|
||||
cameraCellBgColor = color
|
||||
return self
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
func adjustSliderNormalColor(_ color: UIColor) -> ZLPhotoUIConfiguration {
|
||||
adjustSliderNormalColor = color
|
||||
return self
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
func adjustSliderTintColor(_ color: UIColor) -> ZLPhotoUIConfiguration {
|
||||
adjustSliderTintColor = color
|
||||
return self
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
func imageEditorToolTitleNormalColor(_ color: UIColor) -> ZLPhotoUIConfiguration {
|
||||
imageEditorToolTitleNormalColor = color
|
||||
return self
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
func imageEditorToolTitleTintColor(_ color: UIColor) -> ZLPhotoUIConfiguration {
|
||||
imageEditorToolTitleTintColor = color
|
||||
return self
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
func imageEditorToolIconTintColor(_ color: UIColor) -> ZLPhotoUIConfiguration {
|
||||
imageEditorToolIconTintColor = color
|
||||
return self
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
func trashCanBackgroundNormalColor(_ color: UIColor) -> ZLPhotoUIConfiguration {
|
||||
trashCanBackgroundNormalColor = color
|
||||
return self
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
func trashCanBackgroundTintColor(_ color: UIColor) -> ZLPhotoUIConfiguration {
|
||||
trashCanBackgroundTintColor = color
|
||||
return self
|
||||
}
|
||||
}
|
||||
460
Pods/ZLPhotoBrowser/Sources/General/ZLPhotoUIConfiguration.swift
generated
Normal file
460
Pods/ZLPhotoBrowser/Sources/General/ZLPhotoUIConfiguration.swift
generated
Normal file
@@ -0,0 +1,460 @@
|
||||
//
|
||||
// ZLPhotoUIConfiguration.swift
|
||||
// ZLPhotoBrowser
|
||||
//
|
||||
// Created by long on 2022/4/18.
|
||||
//
|
||||
// Copyright (c) 2020 Long Zhang <495181165@qq.com>
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
// THE SOFTWARE.
|
||||
|
||||
import UIKit
|
||||
|
||||
/// Custom UI configuration (include colors, images, text, font)
|
||||
@objcMembers
|
||||
public class ZLPhotoUIConfiguration: NSObject {
|
||||
@objc public enum CancelButtonStyle: Int {
|
||||
case text
|
||||
case image
|
||||
}
|
||||
|
||||
private static var single = ZLPhotoUIConfiguration()
|
||||
|
||||
public class func `default`() -> ZLPhotoUIConfiguration {
|
||||
return ZLPhotoUIConfiguration.single
|
||||
}
|
||||
|
||||
public class func resetConfiguration() {
|
||||
ZLPhotoUIConfiguration.single = ZLPhotoUIConfiguration()
|
||||
}
|
||||
|
||||
// MARK: Framework style.
|
||||
|
||||
public var style: ZLPhotoBrowserStyle = .embedAlbumList
|
||||
|
||||
public var statusBarStyle: UIStatusBarStyle = .lightContent
|
||||
|
||||
/// text: Cancel. image: 'x'. Defaults to image.
|
||||
public var navCancelButtonStyle: ZLPhotoUIConfiguration.CancelButtonStyle = .image
|
||||
|
||||
/// Whether to show the status bar when previewing photos. Defaults to false.
|
||||
public var showStatusBarInPreviewInterface = false
|
||||
|
||||
/// HUD style. Defaults to dark.
|
||||
public var hudStyle: ZLProgressHUD.HUDStyle = .dark
|
||||
|
||||
/// Adjust Slider Type
|
||||
public var adjustSliderType: ZLAdjustSliderType = .vertical
|
||||
|
||||
public var cellCornerRadio: CGFloat = 0
|
||||
|
||||
/// Custom alert class. Defaults to nil.
|
||||
public var customAlertClass: ZLCustomAlertProtocol.Type?
|
||||
|
||||
private var pri_columnCount = 4
|
||||
/// The column count when iPhone is in portait mode. Minimum is 2, maximum is 6. Defaults to 4.
|
||||
/// ```
|
||||
/// iPhone landscape mode: columnCount += 2.
|
||||
/// iPad portait mode: columnCount += 2.
|
||||
/// iPad landscape mode: columnCount += 4.
|
||||
/// ```
|
||||
///
|
||||
/// - Note: This property is ignored when using columnCountBlock.
|
||||
public var columnCount: Int {
|
||||
get {
|
||||
pri_columnCount
|
||||
}
|
||||
set {
|
||||
pri_columnCount = min(6, max(newValue, 2))
|
||||
}
|
||||
}
|
||||
|
||||
/// Use this property to customize the column count for `ZLThumbnailViewController`.
|
||||
/// This property is recommended.
|
||||
public var columnCountBlock: ((_ collectionViewWidth: CGFloat) -> Int)?
|
||||
|
||||
/// The minimum spacing to use between items in the same row for `ZLThumbnailViewController`.
|
||||
public var minimumInteritemSpacing: CGFloat = 2
|
||||
|
||||
/// The minimum spacing to use between lines of items in the grid for `ZLThumbnailViewController`.
|
||||
public var minimumLineSpacing: CGFloat = 2
|
||||
|
||||
// MARK: Navigation and bottom tool bar
|
||||
|
||||
/// The blur effect of the navigation bar in the album list
|
||||
public var navViewBlurEffectOfAlbumList: UIBlurEffect? = UIBlurEffect(style: .dark)
|
||||
|
||||
/// The blur effect of the navigation bar in the preview interface
|
||||
public var navViewBlurEffectOfPreview: UIBlurEffect? = UIBlurEffect(style: .dark)
|
||||
|
||||
/// The blur effect of the bottom tool bar in the album list
|
||||
public var bottomViewBlurEffectOfAlbumList: UIBlurEffect? = UIBlurEffect(style: .dark)
|
||||
|
||||
/// The blur effect of the bottom tool bar in the preview interface
|
||||
public var bottomViewBlurEffectOfPreview: UIBlurEffect? = UIBlurEffect(style: .dark)
|
||||
|
||||
// MARK: Image properties
|
||||
|
||||
/// Developers can customize images, but the name of the custom image resource must be consistent with the image name in the replaced bundle.
|
||||
/// - example: Developers need to replace the selected and unselected image resources, and the array that needs to be passed in is
|
||||
/// ["zl_btn_selected", "zl_btn_unselected"].
|
||||
public var customImageNames: [String] = [] {
|
||||
didSet {
|
||||
ZLCustomImageDeploy.imageNames = customImageNames
|
||||
}
|
||||
}
|
||||
|
||||
/// Developers can customize images, but the name of the custom image resource must be consistent with the image name in the replaced bundle.
|
||||
/// - example: Developers need to replace the selected and unselected image resources, and the array that needs to be passed in is
|
||||
/// ["zl_btn_selected": selectedImage, "zl_btn_unselected": unselectedImage].
|
||||
public var customImageForKey: [String: UIImage?] = [:] {
|
||||
didSet {
|
||||
customImageForKey.forEach { ZLCustomImageDeploy.imageForKey[$0.key] = $0.value }
|
||||
}
|
||||
}
|
||||
|
||||
/// Developers can customize images, but the name of the custom image resource must be consistent with the image name in the replaced bundle.
|
||||
/// - example: Developers need to replace the selected and unselected image resources, and the array that needs to be passed in is
|
||||
/// ["zl_btn_selected": selectedImage, "zl_btn_unselected": unselectedImage].
|
||||
public var customImageForKey_objc: [String: UIImage] = [:] {
|
||||
didSet {
|
||||
ZLCustomImageDeploy.imageForKey = customImageForKey_objc
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: Language properties
|
||||
|
||||
/// Language for framework.
|
||||
public var languageType: ZLLanguageType = .system {
|
||||
didSet {
|
||||
ZLCustomLanguageDeploy.language = languageType
|
||||
Bundle.resetLanguage()
|
||||
}
|
||||
}
|
||||
|
||||
/// Developers can customize languages.
|
||||
/// - example: If you needs to replace
|
||||
/// key: .hudLoading, value: "loading, waiting please" language,
|
||||
/// The dictionary that needs to be passed in is [.hudLoading: "text to be replaced"].
|
||||
/// - warning: Please pay attention to the placeholders contained in languages when changing, such as %ld, %@.
|
||||
public var customLanguageKeyValue: [ZLLocalLanguageKey: String] = [:] {
|
||||
didSet {
|
||||
ZLCustomLanguageDeploy.deploy = customLanguageKeyValue
|
||||
}
|
||||
}
|
||||
|
||||
/// Developers can customize languages (This property is only for objc).
|
||||
/// - example: If you needs to replace
|
||||
/// key: @"loading", value: @"loading, waiting please" language,
|
||||
/// The dictionary that needs to be passed in is @[@"hudLoading": @"text to be replaced"].
|
||||
/// - warning: Please pay attention to the placeholders contained in languages when changing, such as %ld, %@.
|
||||
public var customLanguageKeyValue_objc: [String: String] = [:] {
|
||||
didSet {
|
||||
var swiftParams: [ZLLocalLanguageKey: String] = [:]
|
||||
customLanguageKeyValue_objc.forEach { key, value in
|
||||
swiftParams[ZLLocalLanguageKey(rawValue: key)] = value
|
||||
}
|
||||
customLanguageKeyValue = swiftParams
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: Font
|
||||
|
||||
/// Font name.
|
||||
public var themeFontName: String? {
|
||||
didSet {
|
||||
ZLCustomFontDeploy.fontName = themeFontName
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: Color properties
|
||||
|
||||
/// The theme color of framework.
|
||||
/// 框架主题色
|
||||
public var themeColor: UIColor = .zl.rgba(7, 213, 101)
|
||||
|
||||
/// Preview selection mode, translucent background color above.
|
||||
/// 预览快速选择模式下,上方透明区域背景色
|
||||
public var sheetTranslucentColor: UIColor = .black.withAlphaComponent(0.1)
|
||||
|
||||
/// Preview selection mode, a background color for `Camera`, `Album`, `Cancel` buttons.
|
||||
/// 预览快速选择模式下,按钮背景颜色
|
||||
public var sheetBtnBgColor: UIColor = .white
|
||||
|
||||
/// Preview selection mode, a text color for `Camera`, `Album`, `Cancel` buttons.
|
||||
/// 预览快速选择模式下,按钮标题颜色
|
||||
public var sheetBtnTitleColor: UIColor = .black
|
||||
|
||||
private var pri_sheetBtnTitleTintColor: UIColor?
|
||||
/// Preview selection mode, cancel button title color when the selection amount is superior than 0.
|
||||
/// 预览快速选择模式下,按钮标题高亮颜色
|
||||
public var sheetBtnTitleTintColor: UIColor {
|
||||
get {
|
||||
pri_sheetBtnTitleTintColor ?? themeColor
|
||||
}
|
||||
set {
|
||||
pri_sheetBtnTitleTintColor = newValue
|
||||
}
|
||||
}
|
||||
|
||||
/// A color for navigation bar.
|
||||
/// 相册列表及小图界面导航条背景色
|
||||
public var navBarColor: UIColor = .zl.rgba(160, 160, 160, 0.65)
|
||||
|
||||
/// A color for navigation bar in preview interface.
|
||||
/// 预览大图界面的导航条背景色
|
||||
public var navBarColorOfPreviewVC: UIColor = .zl.rgba(160, 160, 160, 0.65)
|
||||
|
||||
/// A color for Navigation bar text.
|
||||
/// 相册列表及小图界面导航栏标题颜色
|
||||
public var navTitleColor: UIColor = .white
|
||||
|
||||
/// A color for Navigation bar text of preview vc.
|
||||
/// 预览大图界面导航栏标题颜色
|
||||
public var navTitleColorOfPreviewVC: UIColor = .white
|
||||
|
||||
/// The background color of the title view when the frame style is embedAlbumList.
|
||||
/// 下拉选择相册列表模式下,选择区域的背景色
|
||||
public var navEmbedTitleViewBgColor: UIColor = .zl.rgba(80, 80, 80)
|
||||
|
||||
/// A color for background in album list.
|
||||
/// 相册列表背景色
|
||||
public var albumListBgColor: UIColor = .zl.rgba(45, 45, 45)
|
||||
|
||||
/// A color of the translucent area below the embed album list.
|
||||
/// 嵌入式相册列表下方透明区域颜色
|
||||
public var embedAlbumListTranslucentColor: UIColor = .black.withAlphaComponent(0.8)
|
||||
|
||||
/// A color for album list title label.
|
||||
/// 相册列表标题颜色
|
||||
public var albumListTitleColor: UIColor = .white
|
||||
|
||||
/// A color for album list count label.
|
||||
/// 相册列表数量label的颜色
|
||||
public var albumListCountColor: UIColor = .zl.rgba(180, 180, 180)
|
||||
|
||||
/// A color for album list separator.
|
||||
/// 相册列表分割线颜色
|
||||
public var separatorColor: UIColor = .zl.rgba(60, 60, 60)
|
||||
|
||||
/// A color for background in thumbnail interface.
|
||||
/// 相册小图界面背景色
|
||||
public var thumbnailBgColor: UIColor = .zl.rgba(50, 50, 50)
|
||||
|
||||
/// A color for background in preview interface..
|
||||
/// 预览大图界面背景色
|
||||
public var previewVCBgColor: UIColor = .black
|
||||
|
||||
/// A color for background in bottom tool view.
|
||||
/// 相册小图界面底部工具条背景色
|
||||
public var bottomToolViewBgColor: UIColor = .zl.rgba(35, 35, 35, 0.3)
|
||||
|
||||
/// A color for background in bottom tool view in preview interface.
|
||||
/// 预览大图界面底部工具条背景色
|
||||
public var bottomToolViewBgColorOfPreviewVC: UIColor = .zl.rgba(35, 35, 35, 0.3)
|
||||
|
||||
/// The normal state title color of bottom tool view buttons. Without done button.
|
||||
/// 相册小图界面底部按钮可交互状态下标题颜色,不包括 `完成` 按钮
|
||||
public var bottomToolViewBtnNormalTitleColor: UIColor = .white
|
||||
|
||||
/// The normal state title color of bottom tool view done button.
|
||||
/// 相册小图界面底部 `完成` 按钮可交互状态下标题颜色
|
||||
public var bottomToolViewDoneBtnNormalTitleColor: UIColor = .white
|
||||
|
||||
/// The normal state title color of bottom tool view buttons in preview interface. Without done button.
|
||||
/// 预览大图界面底部按钮可交互状态下标题颜色,不包括 `完成` 按钮
|
||||
public var bottomToolViewBtnNormalTitleColorOfPreviewVC: UIColor = .white
|
||||
|
||||
/// The normal state title color of bottom tool view done button.
|
||||
/// 预览大图界面底部 `完成` 按钮可交互状态下标题颜色
|
||||
public var bottomToolViewDoneBtnNormalTitleColorOfPreviewVC: UIColor = .white
|
||||
|
||||
/// The disable state title color of bottom tool view buttons. Without done button.
|
||||
/// 相册小图界面底部按钮不可交互状态下标题颜色,不包括 `完成` 按钮
|
||||
public var bottomToolViewBtnDisableTitleColor: UIColor = .zl.rgba(168, 168, 168)
|
||||
|
||||
/// The disable state title color of bottom tool view done button.
|
||||
/// 相册小图界面底部 `完成` 按钮不可交互状态下标题颜色
|
||||
public var bottomToolViewDoneBtnDisableTitleColor: UIColor = .zl.rgba(168, 168, 168)
|
||||
|
||||
/// The disable state title color of bottom tool view buttons in preview interface. Without done button.
|
||||
/// 预览大图界面底部按钮不可交互状态下标题颜色,不包括 `完成` 按钮
|
||||
public var bottomToolViewBtnDisableTitleColorOfPreviewVC: UIColor = .zl.rgba(168, 168, 168)
|
||||
|
||||
/// The disable state title color of bottom tool view done button in preview interface.
|
||||
/// 预览大图界面底部 `完成` 按钮不可交互状态下标题颜色
|
||||
public var bottomToolViewDoneBtnDisableTitleColorOfPreviewVC: UIColor = .zl.rgba(168, 168, 168)
|
||||
|
||||
private var pri_bottomToolViewBtnNormalBgColor: UIColor?
|
||||
/// The normal state background color of bottom tool view buttons.
|
||||
/// 相册小图界面底部按钮可交互状态下背景色
|
||||
public var bottomToolViewBtnNormalBgColor: UIColor {
|
||||
get {
|
||||
pri_bottomToolViewBtnNormalBgColor ?? themeColor
|
||||
}
|
||||
set {
|
||||
pri_bottomToolViewBtnNormalBgColor = newValue
|
||||
}
|
||||
}
|
||||
|
||||
private var pri_bottomToolViewBtnNormalBgColorOfPreviewVC: UIColor?
|
||||
/// The normal state background color of bottom tool view buttons in preview interface.
|
||||
/// 预览大图界面底部按钮可交互状态下背景色
|
||||
public var bottomToolViewBtnNormalBgColorOfPreviewVC: UIColor {
|
||||
get {
|
||||
pri_bottomToolViewBtnNormalBgColorOfPreviewVC ?? themeColor
|
||||
}
|
||||
set {
|
||||
pri_bottomToolViewBtnNormalBgColorOfPreviewVC = newValue
|
||||
}
|
||||
}
|
||||
|
||||
/// The disable state background color of bottom tool view buttons.
|
||||
/// 相册小图界面底部按钮不可交互状态下背景色
|
||||
public var bottomToolViewBtnDisableBgColor: UIColor = .zl.rgba(50, 50, 50)
|
||||
|
||||
/// The disable state background color of bottom tool view buttons in preview interface.
|
||||
/// 预览大图界面底部按钮不可交互状态下背景色
|
||||
public var bottomToolViewBtnDisableBgColorOfPreviewVC: UIColor = .zl.rgba(50, 50, 50)
|
||||
|
||||
/// With iOS14 limited authority, a color for select more photos at the bottom of the thumbnail interface.
|
||||
/// iOS14 limited权限下,下方提示选择更多图片信息文字的颜色
|
||||
public var limitedAuthorityTipsColor: UIColor = .white
|
||||
|
||||
private var pri_cameraRecodeProgressColor: UIColor?
|
||||
/// The record progress color of custom camera.
|
||||
/// 自定义相机录制视频时进度条颜色
|
||||
public var cameraRecodeProgressColor: UIColor {
|
||||
get {
|
||||
pri_cameraRecodeProgressColor ?? themeColor
|
||||
}
|
||||
set {
|
||||
pri_cameraRecodeProgressColor = newValue
|
||||
}
|
||||
}
|
||||
|
||||
/// Mask layer color of selected cell.
|
||||
/// 已选择照片上方遮罩阴影颜色
|
||||
public var selectedMaskColor: UIColor = .black.withAlphaComponent(0.2)
|
||||
|
||||
private var pri_selectedBorderColor: UIColor?
|
||||
/// Border color of selected cell.
|
||||
/// 已选择照片border颜色
|
||||
public var selectedBorderColor: UIColor {
|
||||
get {
|
||||
pri_selectedBorderColor ?? themeColor
|
||||
}
|
||||
set {
|
||||
pri_selectedBorderColor = newValue
|
||||
}
|
||||
}
|
||||
|
||||
/// Mask layer color of the cell that cannot be selected.
|
||||
/// 不可选的照片上方遮罩阴影颜色
|
||||
public var invalidMaskColor: UIColor = .white.withAlphaComponent(0.5)
|
||||
|
||||
/// The text color of selected cell index label.
|
||||
/// 已选照片右上角序号label背景色
|
||||
public var indexLabelTextColor: UIColor = .white
|
||||
|
||||
private var pri_indexLabelBgColor: UIColor?
|
||||
/// The background color of selected cell index label.
|
||||
/// 已选照片右上角序号label背景色
|
||||
public var indexLabelBgColor: UIColor {
|
||||
get {
|
||||
pri_indexLabelBgColor ?? themeColor
|
||||
}
|
||||
set {
|
||||
pri_indexLabelBgColor = newValue
|
||||
}
|
||||
}
|
||||
|
||||
/// The background color of camera cell inside album.
|
||||
/// 相册小图界面拍照按钮背景色
|
||||
public var cameraCellBgColor: UIColor = .zl.rgba(76, 76, 76)
|
||||
|
||||
/// The normal color of adjust slider.
|
||||
/// 编辑图片,调整饱和度、对比度、亮度时,右侧slider背景色
|
||||
public var adjustSliderNormalColor: UIColor = .white
|
||||
|
||||
private var pri_adjustSliderTintColor: UIColor?
|
||||
/// The tint color of adjust slider.
|
||||
/// 编辑图片,调整饱和度、对比度、亮度时,右侧slider背景高亮色
|
||||
public var adjustSliderTintColor: UIColor {
|
||||
get {
|
||||
pri_adjustSliderTintColor ?? themeColor
|
||||
}
|
||||
set {
|
||||
pri_adjustSliderTintColor = newValue
|
||||
}
|
||||
}
|
||||
|
||||
/// The normal color of the title below the various tools in the image editor.
|
||||
/// 图片编辑器中各种工具下方标题普通状态下的颜色
|
||||
public var imageEditorToolTitleNormalColor: UIColor = .zl.rgba(160, 160, 160)
|
||||
|
||||
/// The tint color of the title below the various tools in the image editor.
|
||||
/// 图片编辑器中各种工具下方标题高亮状态下的颜色
|
||||
public var imageEditorToolTitleTintColor: UIColor = .white
|
||||
|
||||
/// The tint color of the image editor tool icons.
|
||||
/// 图片编辑器中各种工具图标高亮状态下的颜色
|
||||
public var imageEditorToolIconTintColor: UIColor?
|
||||
|
||||
/// Background color of trash can in image editor.
|
||||
/// 编辑器中垃圾箱普通状态下的颜色
|
||||
public var trashCanBackgroundNormalColor: UIColor = .zl.rgba(40, 40, 40, 0.8)
|
||||
|
||||
/// Background tint color of trash can in image editor.
|
||||
/// 编辑器中垃圾箱高亮状态下的颜色
|
||||
public var trashCanBackgroundTintColor: UIColor = .zl.rgba(241, 79, 79, 0.98)
|
||||
}
|
||||
|
||||
/// Font deploy
|
||||
enum ZLCustomFontDeploy {
|
||||
static var fontName: String?
|
||||
}
|
||||
|
||||
/// Image source deploy
|
||||
enum ZLCustomImageDeploy {
|
||||
static var imageNames: [String] = []
|
||||
|
||||
static var imageForKey: [String: UIImage] = [:]
|
||||
}
|
||||
|
||||
@objc public enum ZLPhotoBrowserStyle: Int {
|
||||
/// The album list is embedded in the navigation of the thumbnail interface, click the drop-down display.
|
||||
case embedAlbumList
|
||||
|
||||
/// The display relationship between the album list and the thumbnail interface is push.
|
||||
case externalAlbumList
|
||||
}
|
||||
|
||||
/// Language deploy
|
||||
enum ZLCustomLanguageDeploy {
|
||||
static var language: ZLLanguageType = .system
|
||||
|
||||
static var deploy: [ZLLocalLanguageKey: String] = [:]
|
||||
}
|
||||
|
||||
/// Adjust slider type
|
||||
@objc public enum ZLAdjustSliderType: Int {
|
||||
case vertical
|
||||
case horizontal
|
||||
}
|
||||
185
Pods/ZLPhotoBrowser/Sources/General/ZLProgressHUD.swift
generated
Normal file
185
Pods/ZLPhotoBrowser/Sources/General/ZLProgressHUD.swift
generated
Normal file
@@ -0,0 +1,185 @@
|
||||
//
|
||||
// ZLProgressHUD.swift
|
||||
// ZLPhotoBrowser
|
||||
//
|
||||
// Created by long on 2020/8/17.
|
||||
//
|
||||
// Copyright (c) 2020 Long Zhang <495181165@qq.com>
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
// THE SOFTWARE.
|
||||
|
||||
import UIKit
|
||||
|
||||
public class ZLProgressHUD: UIView {
|
||||
@objc public enum HUDStyle: Int {
|
||||
case light
|
||||
case lightBlur
|
||||
case dark
|
||||
case darkBlur
|
||||
|
||||
var bgColor: UIColor {
|
||||
switch self {
|
||||
case .light:
|
||||
return .white
|
||||
case .dark:
|
||||
return .darkGray
|
||||
case .lightBlur:
|
||||
return UIColor.white.withAlphaComponent(0.8)
|
||||
case .darkBlur:
|
||||
return UIColor.darkGray.withAlphaComponent(0.8)
|
||||
}
|
||||
}
|
||||
|
||||
var icon: UIImage? {
|
||||
switch self {
|
||||
case .light, .lightBlur:
|
||||
return .zl.getImage("zl_loading_dark")
|
||||
case .dark, .darkBlur:
|
||||
return .zl.getImage("zl_loading_light")
|
||||
}
|
||||
}
|
||||
|
||||
var textColor: UIColor {
|
||||
switch self {
|
||||
case .light, .lightBlur:
|
||||
return .black
|
||||
case .dark, .darkBlur:
|
||||
return .white
|
||||
}
|
||||
}
|
||||
|
||||
var blurEffectStyle: UIBlurEffect.Style? {
|
||||
switch self {
|
||||
case .light, .dark:
|
||||
return nil
|
||||
case .lightBlur:
|
||||
return .extraLight
|
||||
case .darkBlur:
|
||||
return .dark
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private let style: ZLProgressHUD.HUDStyle
|
||||
|
||||
private lazy var loadingView = UIImageView(image: style.icon)
|
||||
|
||||
private var timer: Timer?
|
||||
|
||||
public var timeoutBlock: (() -> Void)?
|
||||
|
||||
deinit {
|
||||
zl_debugPrint("ZLProgressHUD deinit")
|
||||
cleanTimer()
|
||||
}
|
||||
|
||||
public init(style: ZLProgressHUD.HUDStyle) {
|
||||
self.style = style
|
||||
super.init(frame: UIScreen.main.bounds)
|
||||
setupUI()
|
||||
}
|
||||
|
||||
@available(*, unavailable)
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
private func setupUI() {
|
||||
let view = UIView(frame: CGRect(x: 0, y: 0, width: 135, height: 135))
|
||||
view.layer.masksToBounds = true
|
||||
view.layer.cornerRadius = 12
|
||||
view.backgroundColor = style.bgColor
|
||||
view.clipsToBounds = true
|
||||
view.center = center
|
||||
|
||||
if let effectStyle = style.blurEffectStyle {
|
||||
let effect = UIBlurEffect(style: effectStyle)
|
||||
let effectView = UIVisualEffectView(effect: effect)
|
||||
effectView.frame = view.bounds
|
||||
view.addSubview(effectView)
|
||||
}
|
||||
|
||||
loadingView.frame = CGRect(x: 135 / 2 - 20, y: 27, width: 40, height: 40)
|
||||
view.addSubview(loadingView)
|
||||
|
||||
let label = UILabel(frame: CGRect(x: 0, y: 85, width: view.bounds.width, height: 30))
|
||||
label.textAlignment = .center
|
||||
label.textColor = style.textColor
|
||||
label.font = .zl.font(ofSize: 16)
|
||||
label.text = localLanguageTextValue(.hudLoading)
|
||||
view.addSubview(label)
|
||||
|
||||
addSubview(view)
|
||||
}
|
||||
|
||||
private func startAnimation() {
|
||||
let animation = CABasicAnimation(keyPath: "transform.rotation.z")
|
||||
animation.fromValue = 0
|
||||
animation.toValue = CGFloat.pi * 2
|
||||
animation.duration = 0.8
|
||||
animation.repeatCount = .infinity
|
||||
animation.fillMode = .forwards
|
||||
animation.isRemovedOnCompletion = false
|
||||
loadingView.layer.add(animation, forKey: nil)
|
||||
}
|
||||
|
||||
public func show(
|
||||
in view: UIView? = UIApplication.shared.keyWindow,
|
||||
timeout: TimeInterval = 100
|
||||
) {
|
||||
ZLMainAsync {
|
||||
self.startAnimation()
|
||||
view?.addSubview(self)
|
||||
}
|
||||
if timeout > 0 {
|
||||
cleanTimer()
|
||||
timer = Timer.scheduledTimer(timeInterval: timeout, target: ZLWeakProxy(target: self), selector: #selector(timeout(_:)), userInfo: nil, repeats: false)
|
||||
RunLoop.current.add(timer!, forMode: .default)
|
||||
}
|
||||
}
|
||||
|
||||
public func hide() {
|
||||
cleanTimer()
|
||||
ZLMainAsync {
|
||||
self.loadingView.layer.removeAllAnimations()
|
||||
self.removeFromSuperview()
|
||||
}
|
||||
}
|
||||
|
||||
@objc func timeout(_ timer: Timer) {
|
||||
timeoutBlock?()
|
||||
hide()
|
||||
}
|
||||
|
||||
func cleanTimer() {
|
||||
timer?.invalidate()
|
||||
timer = nil
|
||||
}
|
||||
}
|
||||
|
||||
public extension ZLProgressHUD {
|
||||
class func show(
|
||||
in view: UIView? = UIApplication.shared.keyWindow,
|
||||
timeout: TimeInterval = 100
|
||||
) -> ZLProgressHUD {
|
||||
let hud = ZLProgressHUD(style: ZLPhotoUIConfiguration.default().hudStyle)
|
||||
hud.show(in: view, timeout: timeout)
|
||||
return hud
|
||||
}
|
||||
}
|
||||
67
Pods/ZLPhotoBrowser/Sources/General/ZLProgressView.swift
generated
Normal file
67
Pods/ZLPhotoBrowser/Sources/General/ZLProgressView.swift
generated
Normal file
@@ -0,0 +1,67 @@
|
||||
//
|
||||
// ZLProgressView.swift
|
||||
// ZLPhotoBrowser
|
||||
//
|
||||
// Created by long on 2020/8/13.
|
||||
//
|
||||
// Copyright (c) 2020 Long Zhang <495181165@qq.com>
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
// THE SOFTWARE.
|
||||
|
||||
import UIKit
|
||||
|
||||
class ZLProgressView: UIView {
|
||||
|
||||
private lazy var progressLayer: CAShapeLayer = {
|
||||
let layer = CAShapeLayer()
|
||||
layer.fillColor = UIColor.clear.cgColor
|
||||
layer.strokeColor = UIColor.white.cgColor
|
||||
layer.lineCap = .round
|
||||
layer.lineWidth = 4
|
||||
return layer
|
||||
}()
|
||||
|
||||
var progress: CGFloat = 0 {
|
||||
didSet {
|
||||
self.setNeedsDisplay()
|
||||
}
|
||||
}
|
||||
|
||||
override init(frame: CGRect) {
|
||||
super.init(frame: frame)
|
||||
|
||||
backgroundColor = .clear
|
||||
layer.addSublayer(progressLayer)
|
||||
}
|
||||
|
||||
@available(*, unavailable)
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
override func draw(_ rect: CGRect) {
|
||||
let center = CGPoint(x: rect.width / 2, y: rect.height / 2)
|
||||
let radius = rect.width / 2
|
||||
let end = -(.pi / 2) + (.pi * 2 * progress)
|
||||
progressLayer.frame = bounds
|
||||
let path = UIBezierPath(arcCenter: center, radius: radius, startAngle: -(.pi / 2), endAngle: end, clockwise: true)
|
||||
progressLayer.path = path.cgPath
|
||||
}
|
||||
|
||||
}
|
||||
58
Pods/ZLPhotoBrowser/Sources/General/ZLResultModel.swift
generated
Normal file
58
Pods/ZLPhotoBrowser/Sources/General/ZLResultModel.swift
generated
Normal file
@@ -0,0 +1,58 @@
|
||||
//
|
||||
// ZLResultModel.swift
|
||||
// ZLPhotoBrowser
|
||||
//
|
||||
// Created by long on 2022/9/7.
|
||||
//
|
||||
// Copyright (c) 2020 Long Zhang <495181165@qq.com>
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
// THE SOFTWARE.
|
||||
|
||||
import UIKit
|
||||
import Photos
|
||||
|
||||
public class ZLResultModel: NSObject {
|
||||
@objc public let asset: PHAsset
|
||||
|
||||
@objc public let image: UIImage
|
||||
|
||||
/// Whether the picture has been edited. Always false when `saveNewImageAfterEdit = true`.
|
||||
@objc public let isEdited: Bool
|
||||
|
||||
/// Content of the last edit. Always nil when `saveNewImageAfterEdit = true`.
|
||||
@objc public let editModel: ZLEditImageModel?
|
||||
|
||||
/// The order in which the user selects the models in the album. This index is not necessarily equal to the order of the model's index in the array, as some PHAssets requests may fail.
|
||||
@objc public let index: Int
|
||||
|
||||
@objc public init(asset: PHAsset, image: UIImage, isEdited: Bool, editModel: ZLEditImageModel? = nil, index: Int) {
|
||||
self.asset = asset
|
||||
self.image = image
|
||||
self.isEdited = isEdited
|
||||
self.editModel = editModel
|
||||
self.index = index
|
||||
super.init()
|
||||
}
|
||||
}
|
||||
|
||||
extension ZLResultModel {
|
||||
static func ==(lhs: ZLResultModel, rhs: ZLResultModel) -> Bool {
|
||||
return lhs.asset == rhs.asset
|
||||
}
|
||||
}
|
||||
300
Pods/ZLPhotoBrowser/Sources/General/ZLThumbnailPhotoCell.swift
generated
Normal file
300
Pods/ZLPhotoBrowser/Sources/General/ZLThumbnailPhotoCell.swift
generated
Normal file
@@ -0,0 +1,300 @@
|
||||
//
|
||||
// ZLThumbnailPhotoCell.swift
|
||||
// ZLPhotoBrowser
|
||||
//
|
||||
// Created by long on 2020/8/12.
|
||||
//
|
||||
// Copyright (c) 2020 Long Zhang <495181165@qq.com>
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
// THE SOFTWARE.
|
||||
|
||||
import UIKit
|
||||
import Photos
|
||||
|
||||
class ZLThumbnailPhotoCell: UICollectionViewCell {
|
||||
private lazy var containerView = UIView()
|
||||
|
||||
private lazy var bottomShadowView = UIImageView(image: .zl.getImage("zl_shadow"))
|
||||
|
||||
private lazy var videoTag = UIImageView(image: .zl.getImage("zl_video"))
|
||||
|
||||
private lazy var livePhotoTag = UIImageView(image: .zl.getImage("zl_livePhoto"))
|
||||
|
||||
private lazy var editImageTag = UIImageView(image: .zl.getImage("zl_editImage_tag"))
|
||||
|
||||
private lazy var descLabel: UILabel = {
|
||||
let label = UILabel()
|
||||
label.font = .zl.font(ofSize: 13)
|
||||
label.textAlignment = .right
|
||||
label.textColor = .white
|
||||
return label
|
||||
}()
|
||||
|
||||
private lazy var progressView: ZLProgressView = {
|
||||
let view = ZLProgressView()
|
||||
view.isHidden = true
|
||||
return view
|
||||
}()
|
||||
|
||||
private var imageIdentifier: String = ""
|
||||
|
||||
private var smallImageRequestID: PHImageRequestID = PHInvalidImageRequestID
|
||||
|
||||
private var bigImageReqeustID: PHImageRequestID = PHInvalidImageRequestID
|
||||
|
||||
lazy var imageView: UIImageView = {
|
||||
let view = UIImageView()
|
||||
view.contentMode = .scaleAspectFill
|
||||
view.clipsToBounds = true
|
||||
return view
|
||||
}()
|
||||
|
||||
lazy var btnSelect: ZLEnlargeButton = {
|
||||
let btn = ZLEnlargeButton(type: .custom)
|
||||
btn.setBackgroundImage(.zl.getImage("zl_btn_unselected"), for: .normal)
|
||||
btn.setBackgroundImage(.zl.getImage("zl_btn_selected"), for: .selected)
|
||||
btn.addTarget(self, action: #selector(btnSelectClick), for: .touchUpInside)
|
||||
btn.enlargeInsets = UIEdgeInsets(top: 5, left: 10, bottom: 10, right: 5)
|
||||
return btn
|
||||
}()
|
||||
|
||||
lazy var coverView: UIView = {
|
||||
let view = UIView()
|
||||
view.isUserInteractionEnabled = false
|
||||
view.isHidden = true
|
||||
return view
|
||||
}()
|
||||
|
||||
lazy var indexLabel: UILabel = {
|
||||
let label = UILabel()
|
||||
label.layer.cornerRadius = 23.0 / 2
|
||||
label.layer.masksToBounds = true
|
||||
label.textColor = .zl.indexLabelTextColor
|
||||
label.backgroundColor = .zl.indexLabelBgColor
|
||||
label.font = .zl.font(ofSize: 14)
|
||||
label.adjustsFontSizeToFitWidth = true
|
||||
label.minimumScaleFactor = 0.5
|
||||
label.textAlignment = .center
|
||||
return label
|
||||
}()
|
||||
|
||||
var enableSelect = true
|
||||
|
||||
var selectedBlock: ((Bool) -> Void)?
|
||||
|
||||
var model: ZLPhotoModel! {
|
||||
didSet {
|
||||
configureCell()
|
||||
}
|
||||
}
|
||||
|
||||
var index: Int = 0 {
|
||||
didSet {
|
||||
indexLabel.text = String(index)
|
||||
}
|
||||
}
|
||||
|
||||
deinit {
|
||||
zl_debugPrint("ZLThumbnailPhotoCell deinit")
|
||||
}
|
||||
|
||||
override init(frame: CGRect) {
|
||||
super.init(frame: frame)
|
||||
setupUI()
|
||||
}
|
||||
|
||||
@available(*, unavailable)
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
func setupUI() {
|
||||
contentView.addSubview(imageView)
|
||||
contentView.addSubview(containerView)
|
||||
containerView.addSubview(coverView)
|
||||
containerView.addSubview(btnSelect)
|
||||
btnSelect.addSubview(indexLabel)
|
||||
containerView.addSubview(bottomShadowView)
|
||||
bottomShadowView.addSubview(videoTag)
|
||||
bottomShadowView.addSubview(livePhotoTag)
|
||||
bottomShadowView.addSubview(editImageTag)
|
||||
bottomShadowView.addSubview(descLabel)
|
||||
containerView.addSubview(progressView)
|
||||
|
||||
if ZLPhotoConfiguration.default().showSelectedBorder {
|
||||
layer.borderColor = UIColor.zl.selectedBorderColor.cgColor
|
||||
}
|
||||
}
|
||||
|
||||
override func layoutSubviews() {
|
||||
imageView.frame = bounds
|
||||
|
||||
containerView.frame = bounds
|
||||
coverView.frame = bounds
|
||||
btnSelect.frame = CGRect(x: bounds.width - 30, y: 8, width: 23, height: 23)
|
||||
indexLabel.frame = btnSelect.bounds
|
||||
bottomShadowView.frame = CGRect(x: 0, y: bounds.height - 25, width: bounds.width, height: 25)
|
||||
videoTag.frame = CGRect(x: 5, y: 1, width: 20, height: 15)
|
||||
livePhotoTag.frame = CGRect(x: 5, y: -1, width: 20, height: 20)
|
||||
editImageTag.frame = CGRect(x: 5, y: -1, width: 20, height: 20)
|
||||
descLabel.frame = CGRect(x: 30, y: 1, width: bounds.width - 35, height: 17)
|
||||
progressView.frame = CGRect(x: (bounds.width - 20) / 2, y: (bounds.height - 20) / 2, width: 20, height: 20)
|
||||
|
||||
super.layoutSubviews()
|
||||
}
|
||||
|
||||
@objc func btnSelectClick() {
|
||||
btnSelect.layer.removeAllAnimations()
|
||||
if !btnSelect.isSelected, ZLPhotoConfiguration.default().animateSelectBtnWhenSelect {
|
||||
btnSelect.layer.add(ZLAnimationUtils.springAnimation(), forKey: nil)
|
||||
}
|
||||
|
||||
selectedBlock?(btnSelect.isSelected)
|
||||
|
||||
if btnSelect.isSelected {
|
||||
fetchBigImage()
|
||||
} else {
|
||||
progressView.isHidden = true
|
||||
cancelFetchBigImage()
|
||||
}
|
||||
}
|
||||
|
||||
private func configureCell() {
|
||||
if ZLPhotoUIConfiguration.default().cellCornerRadio > 0 {
|
||||
layer.cornerRadius = ZLPhotoUIConfiguration.default().cellCornerRadio
|
||||
layer.masksToBounds = true
|
||||
}
|
||||
|
||||
if model.type == .video {
|
||||
bottomShadowView.isHidden = false
|
||||
videoTag.isHidden = false
|
||||
livePhotoTag.isHidden = true
|
||||
editImageTag.isHidden = true
|
||||
descLabel.text = model.duration
|
||||
} else if model.type == .gif {
|
||||
bottomShadowView.isHidden = !ZLPhotoConfiguration.default().allowSelectGif
|
||||
videoTag.isHidden = true
|
||||
livePhotoTag.isHidden = true
|
||||
editImageTag.isHidden = true
|
||||
descLabel.text = "GIF"
|
||||
} else if model.type == .livePhoto {
|
||||
bottomShadowView.isHidden = !ZLPhotoConfiguration.default().allowSelectLivePhoto
|
||||
videoTag.isHidden = true
|
||||
livePhotoTag.isHidden = false
|
||||
editImageTag.isHidden = true
|
||||
descLabel.text = "Live"
|
||||
} else {
|
||||
if let _ = model.editImage {
|
||||
bottomShadowView.isHidden = false
|
||||
videoTag.isHidden = true
|
||||
livePhotoTag.isHidden = true
|
||||
editImageTag.isHidden = false
|
||||
descLabel.text = ""
|
||||
} else {
|
||||
bottomShadowView.isHidden = true
|
||||
}
|
||||
}
|
||||
|
||||
let showSelBtn: Bool
|
||||
if ZLPhotoConfiguration.default().maxSelectCount > 1 {
|
||||
if !ZLPhotoConfiguration.default().allowMixSelect {
|
||||
showSelBtn = model.type.rawValue < ZLPhotoModel.MediaType.video.rawValue
|
||||
} else {
|
||||
showSelBtn = true
|
||||
}
|
||||
} else {
|
||||
showSelBtn = ZLPhotoConfiguration.default().showSelectBtnWhenSingleSelect
|
||||
}
|
||||
|
||||
btnSelect.isHidden = !showSelBtn
|
||||
btnSelect.isUserInteractionEnabled = showSelBtn
|
||||
btnSelect.isSelected = model.isSelected
|
||||
|
||||
if model.isSelected {
|
||||
fetchBigImage()
|
||||
} else {
|
||||
cancelFetchBigImage()
|
||||
}
|
||||
|
||||
if let ei = model.editImage {
|
||||
imageView.image = ei
|
||||
} else {
|
||||
fetchSmallImage()
|
||||
}
|
||||
}
|
||||
|
||||
private func fetchSmallImage() {
|
||||
let size: CGSize
|
||||
let maxSideLength = bounds.width * 2
|
||||
if model.whRatio > 1 {
|
||||
let w = maxSideLength * model.whRatio
|
||||
size = CGSize(width: w, height: maxSideLength)
|
||||
} else {
|
||||
let h = maxSideLength / model.whRatio
|
||||
size = CGSize(width: maxSideLength, height: h)
|
||||
}
|
||||
|
||||
if smallImageRequestID > PHInvalidImageRequestID {
|
||||
PHImageManager.default().cancelImageRequest(smallImageRequestID)
|
||||
}
|
||||
|
||||
imageIdentifier = model.ident
|
||||
imageView.image = nil
|
||||
smallImageRequestID = ZLPhotoManager.fetchImage(for: model.asset, size: size, completion: { [weak self] image, isDegraded in
|
||||
if self?.imageIdentifier == self?.model.ident {
|
||||
self?.imageView.image = image
|
||||
}
|
||||
if !isDegraded {
|
||||
self?.smallImageRequestID = PHInvalidImageRequestID
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
private func fetchBigImage() {
|
||||
cancelFetchBigImage()
|
||||
|
||||
bigImageReqeustID = ZLPhotoManager.fetchOriginalImageData(for: model.asset, progress: { [weak self] progress, _, _, _ in
|
||||
if self?.model.isSelected == true {
|
||||
self?.progressView.isHidden = false
|
||||
self?.progressView.progress = max(0.1, progress)
|
||||
self?.imageView.alpha = 0.5
|
||||
if progress >= 1 {
|
||||
self?.resetProgressViewStatus()
|
||||
}
|
||||
} else {
|
||||
self?.cancelFetchBigImage()
|
||||
}
|
||||
}, completion: { [weak self] _, _, _ in
|
||||
self?.resetProgressViewStatus()
|
||||
})
|
||||
}
|
||||
|
||||
private func cancelFetchBigImage() {
|
||||
if bigImageReqeustID > PHInvalidImageRequestID {
|
||||
PHImageManager.default().cancelImageRequest(bigImageReqeustID)
|
||||
}
|
||||
resetProgressViewStatus()
|
||||
}
|
||||
|
||||
private func resetProgressViewStatus() {
|
||||
progressView.isHidden = true
|
||||
imageView.alpha = 1
|
||||
}
|
||||
|
||||
}
|
||||
1685
Pods/ZLPhotoBrowser/Sources/General/ZLThumbnailViewController.swift
generated
Normal file
1685
Pods/ZLPhotoBrowser/Sources/General/ZLThumbnailViewController.swift
generated
Normal file
File diff suppressed because it is too large
Load Diff
233
Pods/ZLPhotoBrowser/Sources/General/ZLVideoManager.swift
generated
Normal file
233
Pods/ZLPhotoBrowser/Sources/General/ZLVideoManager.swift
generated
Normal file
@@ -0,0 +1,233 @@
|
||||
//
|
||||
// ZLVideoManager.swift
|
||||
// ZLPhotoBrowser
|
||||
//
|
||||
// Created by long on 2020/9/23.
|
||||
//
|
||||
// Copyright (c) 2020 Long Zhang <495181165@qq.com>
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
// THE SOFTWARE.
|
||||
|
||||
import UIKit
|
||||
import AVFoundation
|
||||
import Photos
|
||||
|
||||
public class ZLVideoManager: NSObject {
|
||||
class func getVideoExportFilePath(format: String? = nil) -> String {
|
||||
let format = format ?? ZLPhotoConfiguration.default().cameraConfiguration.videoExportType.format
|
||||
return NSTemporaryDirectory().appendingFormat("%@.%@", UUID().uuidString, format)
|
||||
}
|
||||
|
||||
class func exportEditVideo(for asset: AVAsset, range: CMTimeRange, complete: @escaping ((URL?, Error?) -> Void)) {
|
||||
let type: ZLVideoManager.ExportType = ZLPhotoConfiguration.default().cameraConfiguration.videoExportType == .mov ? .mov : .mp4
|
||||
exportVideo(for: asset, range: range, exportType: type, presetName: AVAssetExportPresetPassthrough) { url, error in
|
||||
if url != nil {
|
||||
complete(url!, error)
|
||||
} else {
|
||||
complete(nil, error)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 没有针对不同分辨率视频做处理,仅用于处理相机拍照的视频
|
||||
@objc public class func mergeVideos(fileUrls: [URL], completion: @escaping ((URL?, Error?) -> Void)) {
|
||||
let composition = AVMutableComposition()
|
||||
let assets = fileUrls.map { AVURLAsset(url: $0) }
|
||||
|
||||
var insertTime: CMTime = .zero
|
||||
var assetVideoTracks: [AVAssetTrack] = []
|
||||
|
||||
let compositionVideoTrack = composition.addMutableTrack(withMediaType: .video, preferredTrackID: CMPersistentTrackID())!
|
||||
let compositionAudioTrack = composition.addMutableTrack(withMediaType: .audio, preferredTrackID: CMPersistentTrackID())!
|
||||
|
||||
for asset in assets {
|
||||
do {
|
||||
let timeRange = CMTimeRangeMake(start: .zero, duration: asset.duration)
|
||||
if let videoTrack = asset.tracks(withMediaType: .video).first {
|
||||
try compositionVideoTrack.insertTimeRange(
|
||||
timeRange,
|
||||
of: videoTrack,
|
||||
at: insertTime
|
||||
)
|
||||
|
||||
assetVideoTracks.append(videoTrack)
|
||||
}
|
||||
|
||||
if let audioTrack = asset.tracks(withMediaType: .audio).first {
|
||||
try compositionAudioTrack.insertTimeRange(
|
||||
timeRange,
|
||||
of: audioTrack,
|
||||
at: insertTime
|
||||
)
|
||||
}
|
||||
|
||||
insertTime = CMTimeAdd(insertTime, asset.duration)
|
||||
} catch {
|
||||
completion(nil, NSError.videoMergeError)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
guard assetVideoTracks.count == assets.count else {
|
||||
completion(nil, NSError.videoMergeError)
|
||||
return
|
||||
}
|
||||
|
||||
let renderSize = getNaturalSize(videoTrack: assetVideoTracks[0])
|
||||
|
||||
let videoComposition = AVMutableVideoComposition()
|
||||
videoComposition.instructions = getInstructions(compositionTrack: compositionVideoTrack, assetVideoTracks: assetVideoTracks, assets: assets)
|
||||
videoComposition.frameDuration = assetVideoTracks[0].minFrameDuration
|
||||
videoComposition.renderSize = renderSize
|
||||
videoComposition.renderScale = 1
|
||||
|
||||
guard let exportSession = AVAssetExportSession(asset: composition, presetName: AVAssetExportPreset1280x720) else {
|
||||
completion(nil, NSError.videoMergeError)
|
||||
return
|
||||
}
|
||||
|
||||
let outputUrl = URL(fileURLWithPath: ZLVideoManager.getVideoExportFilePath())
|
||||
exportSession.outputURL = outputUrl
|
||||
exportSession.shouldOptimizeForNetworkUse = true
|
||||
exportSession.outputFileType = ZLPhotoConfiguration.default().cameraConfiguration.videoExportType.avFileType
|
||||
exportSession.videoComposition = videoComposition
|
||||
exportSession.exportAsynchronously(completionHandler: {
|
||||
let suc = exportSession.status == .completed
|
||||
if exportSession.status == .failed {
|
||||
zl_debugPrint("ZLPhotoBrowser: video merge failed: \(exportSession.error?.localizedDescription ?? "")")
|
||||
}
|
||||
ZLMainAsync {
|
||||
completion(suc ? outputUrl : nil, exportSession.error)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
private static func getNaturalSize(videoTrack: AVAssetTrack) -> CGSize {
|
||||
var size = videoTrack.naturalSize
|
||||
if isPortraitVideoTrack(videoTrack) {
|
||||
swap(&size.width, &size.height)
|
||||
}
|
||||
return size
|
||||
}
|
||||
|
||||
private static func getInstructions(
|
||||
compositionTrack: AVMutableCompositionTrack,
|
||||
assetVideoTracks: [AVAssetTrack],
|
||||
assets: [AVURLAsset]
|
||||
) -> [AVMutableVideoCompositionInstruction] {
|
||||
var instructions: [AVMutableVideoCompositionInstruction] = []
|
||||
|
||||
var start: CMTime = .zero
|
||||
for (index, videoTrack) in assetVideoTracks.enumerated() {
|
||||
let asset = assets[index]
|
||||
let layerInstruction = AVMutableVideoCompositionLayerInstruction(assetTrack: compositionTrack)
|
||||
layerInstruction.setTransform(videoTrack.preferredTransform, at: .zero)
|
||||
|
||||
let instruction = AVMutableVideoCompositionInstruction()
|
||||
instruction.timeRange = CMTimeRangeMake(start: start, duration: asset.duration)
|
||||
instruction.layerInstructions = [layerInstruction]
|
||||
instructions.append(instruction)
|
||||
|
||||
start = CMTimeAdd(start, asset.duration)
|
||||
}
|
||||
|
||||
return instructions
|
||||
}
|
||||
|
||||
private static func isPortraitVideoTrack(_ track: AVAssetTrack) -> Bool {
|
||||
let transform = track.preferredTransform
|
||||
let tfA = transform.a
|
||||
let tfB = transform.b
|
||||
let tfC = transform.c
|
||||
let tfD = transform.d
|
||||
|
||||
if (tfA == 0 && tfB == 1 && tfC == -1 && tfD == 0) ||
|
||||
(tfA == 0 && tfB == 1 && tfC == 1 && tfD == 0) ||
|
||||
(tfA == 0 && tfB == -1 && tfC == 1 && tfD == 0) {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: export methods
|
||||
|
||||
public extension ZLVideoManager {
|
||||
@objc class func exportVideo(for asset: PHAsset, exportType: ZLVideoManager.ExportType = .mov, presetName: String = AVAssetExportPresetMediumQuality, complete: @escaping ((URL?, Error?) -> Void)) {
|
||||
guard asset.mediaType == .video else {
|
||||
complete(nil, NSError.videoExportTypeError)
|
||||
return
|
||||
}
|
||||
|
||||
_ = ZLPhotoManager.fetchAVAsset(forVideo: asset) { avAsset, _ in
|
||||
if let set = avAsset {
|
||||
self.exportVideo(for: set, exportType: exportType, presetName: presetName, complete: complete)
|
||||
} else {
|
||||
complete(nil, NSError.videoExportError)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@objc class func exportVideo(for asset: AVAsset, range: CMTimeRange = CMTimeRange(start: .zero, duration: .positiveInfinity), exportType: ZLVideoManager.ExportType = .mov, presetName: String = AVAssetExportPresetMediumQuality, complete: @escaping ((URL?, Error?) -> Void)) {
|
||||
let outputUrl = URL(fileURLWithPath: getVideoExportFilePath(format: exportType.format))
|
||||
guard let exportSession = AVAssetExportSession(asset: asset, presetName: presetName) else {
|
||||
complete(nil, NSError.videoExportError)
|
||||
return
|
||||
}
|
||||
exportSession.outputURL = outputUrl
|
||||
exportSession.outputFileType = exportType.avFileType
|
||||
exportSession.timeRange = range
|
||||
|
||||
exportSession.exportAsynchronously(completionHandler: {
|
||||
let suc = exportSession.status == .completed
|
||||
if exportSession.status == .failed {
|
||||
zl_debugPrint("ZLPhotoBrowser: video export failed: \(exportSession.error?.localizedDescription ?? "")")
|
||||
}
|
||||
ZLMainAsync {
|
||||
complete(suc ? outputUrl : nil, exportSession.error)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
public extension ZLVideoManager {
|
||||
@objc enum ExportType: Int {
|
||||
var format: String {
|
||||
switch self {
|
||||
case .mov:
|
||||
return "mov"
|
||||
case .mp4:
|
||||
return "mp4"
|
||||
}
|
||||
}
|
||||
|
||||
var avFileType: AVFileType {
|
||||
switch self {
|
||||
case .mov:
|
||||
return .mov
|
||||
case .mp4:
|
||||
return .mp4
|
||||
}
|
||||
}
|
||||
|
||||
case mov
|
||||
case mp4
|
||||
}
|
||||
}
|
||||
50
Pods/ZLPhotoBrowser/Sources/General/ZLWeakProxy.swift
generated
Normal file
50
Pods/ZLPhotoBrowser/Sources/General/ZLWeakProxy.swift
generated
Normal file
@@ -0,0 +1,50 @@
|
||||
//
|
||||
// ZLWeakProxy.swift
|
||||
// ZLPhotoBrowser
|
||||
//
|
||||
// Created by long on 2021/3/10.
|
||||
//
|
||||
// Copyright (c) 2020 Long Zhang <495181165@qq.com>
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
// THE SOFTWARE.
|
||||
|
||||
import UIKit
|
||||
|
||||
class ZLWeakProxy: NSObject {
|
||||
|
||||
private weak var target: NSObjectProtocol?
|
||||
|
||||
init(target: NSObjectProtocol) {
|
||||
self.target = target
|
||||
super.init()
|
||||
}
|
||||
|
||||
class func proxy(withTarget target: NSObjectProtocol) -> ZLWeakProxy {
|
||||
return ZLWeakProxy(target: target)
|
||||
}
|
||||
|
||||
override func forwardingTarget(for aSelector: Selector!) -> Any? {
|
||||
return target
|
||||
}
|
||||
|
||||
override func responds(to aSelector: Selector!) -> Bool {
|
||||
return target?.responds(to: aSelector) ?? false
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user