之前用过不错的第三方的放大图片,可以一次浏览多张图片的工具,但懒得找了,反正闲着自己花了大半天时间,做了下面这么个简陋的东西。
- 暂时只支持每次放大(屏幕大小)一个视图/图片,且不能手动缩放。
- 如果是UIImageView,且包含图片,则自动计算长宽比例,否则按原始frame比例放大。
- 最恶心的是处理约束关系!因为需要把原始视图从其superView中移除,会破坏原有的约束关系,所以需要考虑约束的各种可能!因为感觉有点复杂,所以暂时没有做一次浏览多张图片功能。
- 注意,如果如果被放大的视图showView内部有subviews,那么其subviews的布局是未知的(不符合期望的)
Demo地址:https://github.com/jacksgithub99/JacksZoomOut.git
使用方法:
@IBAction func btnClick(_ sender: UIButton) {
if index == 0 {
JKZoomViewsManager.sharedInstance.show(zoomView: greenView)
}else if index == 1 {
JKZoomViewsManager.sharedInstance.show(zoomView: innerView)
}else if index == 2 {
JKZoomViewsManager.sharedInstance.show(zoomView: redView)
}else if index == 3 {
JKZoomViewsManager.sharedInstance.show(zoomView: frameView)
index = -1
}
index += 1
}
//Hello This is Jack!
import UIKit
/*
* 实现图片(任意View)的放大查看。
1.可以是约束布局的View,也可以是坐标(frame)布局的View
2.通过superView.insertSubview(zView, belowSubview: self.replaceView) 和 superView.insertSubview(replaceView, aboveSubview: zView)
保证查看结束后,zView可以恢复原来在父视图中的对应层级(与兄弟视图之间的层级)
3.目前只实现了一次查看一张图片(View)。
4.~~~~~~~~~~~~~
注意,如果如果被放大的视图showView内部有subviews,那么其subviews的布局是未知的(不符合期望的)
~~~~~~~~~~~~~~~
*/
typealias jacksZoomManagerCallback = () -> Void
class JKZoomViewsManager: NSObject {
//图片(视图缩放)
static let zoomViewsManager = JKZoomViewsManager()
class var sharedInstance: JKZoomViewsManager {
return zoomViewsManager
}
//contentView将被添加到keyWindow中全屏展示。把需要显示的showView显示在其上
private let contentView: UIView = UIView(frame: CGRect(x: 0, y: 0, width: UIScreen.main.bounds.width, height: UIScreen.main.bounds.height))
private var showView: UIView? //show(zoomView: UIView)传入的视图,即需要放大的视图
private let replaceView = UIView() //占位用的空白View(当superView对showView有约束时,移除showView之后,需要另一个视图填补其superView缺失的约束)
private var savedConstraints: [NSLayoutConstraint]?
private var isShowing: Bool = false
private var lastOperationDate = Date(timeIntervalSinceNow: -2)
private let lockInterval = 0.4 //防止一次操作未完成就进行另一次操作!(值越大,锁定时间越久,越安全。必须大于执行动画所需的0.25,且要考虑其他运算的时间、卡顿的时间等)
var zoomOutStart: jacksZoomManagerCallback?
var zoomOutEnd: jacksZoomManagerCallback?
override init() {
super.init()
replaceView.backgroundColor = UIColor.clear
contentView.backgroundColor = UIColor.black
contentView.accessibilityIdentifier = "jk_zoom_content_id" //单元测试UITest用到
let tap = UITapGestureRecognizer(target: self, action: #selector(contentViewTap))
contentView.addGestureRecognizer(tap)
}
func show(zoomView: UIView) {
if showView != nil {
return
}
if lastOperationDate.timeIntervalSinceNow > -lockInterval {
return
}
if isShowing {
return
}else {
isShowing = true
}
guard let keyWindow = UIApplication.shared.keyWindow else {
return
}
guard let superView = zoomView.superview else {
return
}
if zoomOutStart != nil {
zoomOutStart!()
}
showView = zoomView
savedConstraints = superView.constraints
replace(zView: zoomView) //占位
keyWindow.addSubview(contentView)
contentView.isHidden = true
var viewW = zoomView.bounds.size.width
var viewH = zoomView.bounds.size.height
let screenW = keyWindow.bounds.size.width
let screenH = keyWindow.bounds.size.height
var finalViewW: CGFloat = 0
var finalViewH: CGFloat = 0
if let imgView = showView as? UIImageView, let image = imgView.image {
let imageSize = image.size
viewW = imageSize.width
viewH = imageSize.height
}
if viewW/viewH > screenW/screenH {
finalViewW = screenW
finalViewH = viewH*finalViewW/viewW
}else{
finalViewH = screenH// - 40.0 //status bar height * 2
finalViewW = viewW*finalViewH/viewH
}
let finalX = (screenW - finalViewW)/2.0
let finalY = (screenH - finalViewH)/2.0
let orgFrame = superView.convert(zoomView.frame, to: keyWindow)//为了动画效果添加的额外代码(从'原始'位置放大!)
contentView.addSubview(zoomView)
DispatchQueue.main.asyncAfter(deadline: .now() + 0.05) {//必须有延时!!否则无法正确渲染。(约束会覆盖frame!)
zoomView.frame = orgFrame//为了动画效果添加的额外代码(从'原始'位置放大!)
self.contentView.isHidden = false
UIView.animate(withDuration: 0.2, animations: {
zoomView.frame = CGRect(x: finalX, y: finalY, width: finalViewW, height: finalViewH)
})
}
lastOperationDate = Date()
}
func hide() {
if lastOperationDate.timeIntervalSinceNow > -lockInterval {
return
}
guard let keyWindow = UIApplication.shared.keyWindow else {
return
}
guard let zView = showView else {
return
}
guard let superView = replaceView.superview else {
return
}
/**********额外动画效果**********/
let orgFrame = superView.convert(self.replaceView.frame, to: keyWindow)
UIView.animate(withDuration: 0.2, animations: {
zView.frame = orgFrame
})
/**********额外动画效果**********/
DispatchQueue.main.asyncAfter(deadline: .now() + 0.2) {//动画结束后
superView.insertSubview(zView, belowSubview: self.replaceView) //注意,replaceView被加在showView的前面!见replace()
zView.frame = self.replaceView.frame
for constraint in self.savedConstraints! {
var isZoomViewConstraint = false
if let first = constraint.firstItem, first.hash == zView.hash {
isZoomViewConstraint = true
}else if let sencond = constraint.secondItem, sencond.hash == zView.hash {
isZoomViewConstraint = true
}
if !isZoomViewConstraint {
continue
}
if superView.constraints.contains(constraint) {
//不可能的事情!
}else {
superView.addConstraint(constraint)
}
}
self.replaceView.removeFromSuperview()
self.contentView.removeFromSuperview()
self.showView = nil
self.isShowing = false
self.lastOperationDate = Date()
if self.zoomOutEnd != nil {
self.zoomOutEnd!()
}
}
}
//用一个空白View替换要放大的zView,保证zView父视图的约束没有被破坏!
private func replace(zView: UIView) {
guard let superView = zView.superview else {
return
}
superView.insertSubview(replaceView, aboveSubview: zView) //注意,replaceView被加在showView的前面!见hide()
//如果没有这句,非约束、纯frame的情况会崩溃(因为replaceView初始化为UIView(),没有frame;会导致1、hide()之后zoomView不能显示,2、第二次执行show()内的zoomView.frame = CGRect(x: finalX, y: finalY, width: finalViewW, height: finalViewH)会崩溃)
replaceView.frame = zView.frame
for constraint in savedConstraints! {
var isZoomViewConstraint = false
if let first = constraint.firstItem, first.hash == zView.hash {
isZoomViewConstraint = true
}else if let sencond = constraint.secondItem, sencond.hash == zView.hash {
isZoomViewConstraint = true
}
if !isZoomViewConstraint {
continue
}
var firstItem: Any!
var secondItem: Any?
if let first = constraint.firstItem, first.hash == zView.hash {
firstItem = replaceView
secondItem = constraint.secondItem
}else if let sencond = constraint.secondItem, sencond.hash == zView.hash {
firstItem = constraint.firstItem
secondItem = replaceView
}
let cpConstraint = NSLayoutConstraint(item: firstItem, attribute: constraint.firstAttribute, relatedBy: constraint.relation, toItem: secondItem, attribute: constraint.secondAttribute, multiplier: constraint.multiplier, constant: constraint.constant)
superView.addConstraint(cpConstraint)
}
let cWidth = NSLayoutConstraint(item: replaceView, attribute: NSLayoutAttribute.width, relatedBy: NSLayoutRelation.equal, toItem: nil, attribute: NSLayoutAttribute.notAnAttribute, multiplier: 1, constant: zView.frame.size.width)
let cHeight = NSLayoutConstraint(item: replaceView, attribute: NSLayoutAttribute.height, relatedBy: NSLayoutRelation.equal, toItem: nil, attribute: NSLayoutAttribute.notAnAttribute, multiplier: 1, constant: zView.frame.size.height)
replaceView.addConstraint(cWidth)
replaceView.addConstraint(cHeight)
}
@objc private func contentViewTap() {
hide()
}
}
网友评论