Swift圖片瀏覽器

這幾天學(xué)習(xí)swift狭归,做一個(gè)swift圖片瀏覽器的demo铝穷。
看了網(wǎng)上很多瀏覽器的寫法,感覺(jué)封裝的最好的是 JXPhotoBrowser 自己也跟著學(xué)習(xí)了一下伴鳖,涉及到:

  • 自定義轉(zhuǎn)場(chǎng)(present和dismiss)

  • imageView的contentMode

  • 手勢(shì)以及手勢(shì)沖突

篇幅較長(zhǎng)鸿市,先馬后看

先看效果吧锯梁,主要是用collectionView實(shí)現(xiàn)
展示3.gif

加入手勢(shì)(單擊、雙擊焰情、拖拽陌凳、捏合)
單擊等.gif
drag.gif

自定義模態(tài)轉(zhuǎn)場(chǎng)動(dòng)畫

在界面跳轉(zhuǎn)的時(shí)候,指定代理為photoAnimation内舟,我們將轉(zhuǎn)場(chǎng)動(dòng)畫相關(guān)代碼合敦,全部交給這個(gè)類來(lái)完成。

photoVc.transitioningDelegate = photoAnimation

首先验游,我們需要了解以下幾個(gè)協(xié)議:

UIViewControllerTransitioningDelegate協(xié)議

通俗來(lái)講充岛,返回一個(gè)實(shí)現(xiàn)了UIViewControllerAnimatedTransitioning協(xié)議的協(xié)議方法的對(duì)象。
并且在方法中耕蝉,實(shí)現(xiàn)present和dismiss動(dòng)畫
@available(iOS 2.0, *)
    optional public func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning?
@available(iOS 2.0, *)
    optional public func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning?

UIViewControllerAnimatedTransitioning協(xié)議

一組用于實(shí)現(xiàn)自定義視圖控制器轉(zhuǎn)換的動(dòng)畫的方法崔梗。
劃重點(diǎn):
在animator對(duì)象中,實(shí)現(xiàn)transitionDuration(使用:)方法來(lái)指定轉(zhuǎn)換的持續(xù)時(shí)間垒在,并實(shí)現(xiàn)animateTransition(使用:)方法來(lái)創(chuàng)建動(dòng)畫本身蒜魄。
您可以提供單獨(dú)的animator對(duì)象來(lái)呈現(xiàn)和解散視圖控制器。(就是自定義present和dismiss動(dòng)畫)

    返回動(dòng)畫執(zhí)行的時(shí)間
    public func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval

    告訴animator執(zhí)行轉(zhuǎn)換動(dòng)畫
    public func animateTransition(using transitionContext: UIViewControllerContextTransitioning)

PS(交互會(huì)用到场躯,這里不用):
要向視圖控制器轉(zhuǎn)換中添加用戶交互权悟,您必須使用animator對(duì)象和交互式animator對(duì)象——使用uiviewcontrollerinteractivetransiating協(xié)議的自定義對(duì)象。

UIViewControllerContextTransitioning協(xié)議

一組為視圖控制器之間的轉(zhuǎn)換動(dòng)畫提供上下文信息的方法
不要在自己的類中采用此協(xié)議推盛,也不要直接創(chuàng)建采用此協(xié)議的對(duì)象。在轉(zhuǎn)換期間谦铃,涉及到轉(zhuǎn)換的animator對(duì)象從UIKit接收到一個(gè)完整配置的上下文對(duì)象耘成。
在定義自定義animator對(duì)象時(shí),總是檢查isAnimated()方法返回的值驹闰,以確定是否應(yīng)該創(chuàng)建動(dòng)畫瘪菌。當(dāng)你創(chuàng)建轉(zhuǎn)換動(dòng)畫時(shí),總是從一個(gè)適當(dāng)?shù)耐瓿蓧K調(diào)用completeTransition(_:)方法嘹朗,讓UIKit知道你所有的動(dòng)畫什么時(shí)候完成师妙。

很明顯,這個(gè)協(xié)議不需要我們自己實(shí)現(xiàn)屹培,只需要在轉(zhuǎn)場(chǎng)動(dòng)畫的時(shí)候默穴,獲取對(duì)應(yīng)的上下文怔檩,其中:
// 充當(dāng)轉(zhuǎn)換中涉及的視圖的父視圖,相當(dāng)于視圖轉(zhuǎn)換的容器
var containerView: UIView
//返回涉及轉(zhuǎn)換的控制器(.from/.to)
func viewController(forKey:  UITransitionContextViewControllerKey)
//返回涉及轉(zhuǎn)換的視圖(.from/.to)
func viewKey:  UITransitionContextViewKey)
//通知系統(tǒng)過(guò)渡動(dòng)畫已經(jīng)完成蓄诽。您必須在動(dòng)畫完成后調(diào)用此方法薛训,以通知系統(tǒng)完成轉(zhuǎn)換動(dòng)畫。您通過(guò)的參數(shù)必須指示動(dòng)畫是否成功完成仑氛。這個(gè)方法的默認(rèn)實(shí)現(xiàn)調(diào)用animator對(duì)象的animationEnded(_:)方法乙埃,讓它有機(jī)會(huì)執(zhí)行任何最后一分鐘的清理。
func completeTransition(_ didComplete: Bool)方法

PS(交互會(huì)用到锯岖,這里不用):
當(dāng)動(dòng)畫開(kāi)始時(shí)介袜,交互式animator對(duì)象必須保存一個(gè)指向上下文對(duì)象的指針。根據(jù)用戶交互出吹,animator對(duì)象然后調(diào)用updateInteractiveTransition(_:)遇伞、finishInteractiveTransition()或cancelInteractiveTransition()方法來(lái)報(bào)告完成動(dòng)畫的進(jìn)度。

動(dòng)畫的實(shí)現(xiàn)細(xì)節(jié)

present具體實(shí)現(xiàn)


    fileprivate func presentAnimation(_ transitionContext:  UIViewControllerContextTransitioning) {
        guard let presentD = presentDelegate, let indexPath = indexPath else {
            return
        }
        //1.取出彈出的View
        guard let presentView = transitionContext.view(forKey: .to) else{ return
        }
        
        //2.加入到containerView中
        transitionContext.containerView.addSubview(presentView)
        //3.獲取彈出的imageView
        let tempImageView = presentD.imageForPresent(indexPath: indexPath)
        tempImageView.frame = presentD.startImageRectForPresent(indexPath: indexPath)
        
        transitionContext.containerView.addSubview(tempImageView)
        //有利于后面拖拽時(shí)趋箩,設(shè)置presentView的alpha
        transitionContext.containerView.backgroundColor = .black
        //        transitionContext.containerView.endImageRectForpresent(indexPath)
        //執(zhí)行動(dòng)畫
        presentView.alpha = 0
        UIView.animate(withDuration: transitionDuration(using: transitionContext), animations: {
            tempImageView.frame = presentD.endImageRectForPresent(indexPath: indexPath)
//            disView?.alpha = 0 如果直接設(shè)置為0赃额,在后面拖拽時(shí),不好設(shè)置alpha
        }) { _ in
            transitionContext.containerView.backgroundColor = .clear
            //上報(bào)動(dòng)畫執(zhí)行完畢
            transitionContext.completeTransition(true)
            tempImageView.removeFromSuperview()
            presentView.alpha = 1
        }
        
    }

dismiss具體實(shí)現(xiàn)

  
    fileprivate func dismissAnimation(_ transitionContext:  UIViewControllerContextTransitioning) {
        guard let dismissD = dismissDelegate , let presentD = presentDelegate else {
            return
        }
        //取出消失的View
        guard let dismissView = transitionContext.view(forKey: .from) else {
            return
        }
        guard let presentVC = transitionContext.viewController(forKey: .to) else {
            print("predent !  error")
            return
        }
        let presentView = presentVC.view
        presentView?.alpha = 0.35

        dismissView.alpha = 0
        //獲取要退出的imageView
        let tempImageV = dismissD.imageForDismiss()
        transitionContext.containerView.addSubview(tempImageV)
        //獲取將要退出的indexPath
        let indexPath = dismissD.indexPathForDissmiss()
        //執(zhí)行動(dòng)畫
        UIView.animate(withDuration: transitionDuration(using: transitionContext), animations: {
            tempImageV.frame = presentD.startImageRectForPresent(indexPath: indexPath)
            dismissView.alpha = 0
            presentView?.alpha = 1
            }) {(_) in
                
                tempImageV.removeFromSuperview()
                dismissView.removeFromSuperview()
                transitionContext.completeTransition(true)
        }
    }

ImageView的contentMode

在顯示圖片的時(shí)候叫确,我們會(huì)遇到長(zhǎng)圖和短圖跳芳,所以在顯示圖片的時(shí)候,我們要設(shè)置imageView的contentMode竹勉。在demo中飞盆,最初用了兩種mode
1.scaleAspectFill // contents scaled to fill with fixed aspect. some portion of content may be clipped.內(nèi)容按比例縮放以填充固定的方面。


scaleAspectFill樣式.png

2.scaleAspectFit // contents scaled to fit with fixed aspect. remainder is transparent內(nèi)容按比例縮放以適應(yīng)固定的方面次乓。剩余部分是透明的


scaleAspectFit樣式.png

最后吓歇,覺(jué)得scaleAspectFill最合適,更具有美感票腰。

手勢(shì)

//單擊
let tap = UITapGestureRecognizer(target: self, action: #selector(closePhototBrowser))
contentView.addGestureRecognizer(tap) 
//雙擊
let doubleTap = UITapGestureRecognizer(target: self, action: #selector(doubleClick(_:)))
doubleTap.numberOfTapsRequired = 2
tap.require(toFail: doubleTap)
contentView.addGestureRecognizer(doubleTap)
//拖拽
let pan = UIPanGestureRecognizer(target: self, action: #selector(panPhotoBrowser(_:)))
pan.delegate = self as UIGestureRecognizerDelegate
scrollView.addGestureRecognizer(pan)
//捏合手勢(shì)
//CollectionView是UIScorllView的子類城看,UIScorllView天生支持pinch捏合手勢(shì),只需要實(shí)現(xiàn)它的代理方法即可
//返回將要縮放的視圖
func viewForZooming(in scrollView: UIScrollView) -> UIView? {
    return imageView
}
/// 需要在縮放的時(shí)候調(diào)用
open func scrollViewDidZoom(_ scrollView: UIScrollView) {
    let imageH = (imageView.image?.size.height)! / (imageView.image?.size.width)! * kScreenWidth
    if imageH < kScreenHeight {
         imageView.center = centerOfContentSize
    }
}

其中杏慰,需要設(shè)置單擊和雙擊的依賴關(guān)系:tap.require(toFail: doubleTap)测柠;pan手勢(shì)需要添加在scrollView中,否則長(zhǎng)圖下拉時(shí)不能退出缘滥。

    在進(jìn)行雙擊圖片縮放時(shí)轰胁,需要用到zoom(to: animated:),對(duì)指定frame進(jìn)行縮放
@objc fileprivate func doubleClick(_ dbTap: UITapGestureRecognizer) {
    // 如果當(dāng)前沒(méi)有任何縮放朝扼,則放大到目標(biāo)比例
    let scale = scrollView.maximumZoomScale
    print(scale)
    // 否則重置到原比例
    if scrollView.zoomScale == 1.0 {
        // 以點(diǎn)擊的位置為中心赃阀,放大
        let pointInView = dbTap.location(in: imageView)
        let w = scrollView.bounds.size.width / scale
        let h = scrollView.bounds.size.height / scale
        let x = pointInView.x - (w / 2.0)
        let y = pointInView.y - (h / 2.0)
        let rect = CGRect(x: x, y: y, width: w, height: h)
        print(rect)
        scrollView.zoom(to: CGRect(x: x, y: y, width: w, height: h), animated: true)
    } else {
        scrollView.setZoomScale(1.0, animated: true)
    }
}

后來(lái)看到一篇文章中介紹這個(gè)方法:

  • -(void)zoomToRect:(CGRect)rect animated:(BOOL)animate
    把從scrollView里截取的矩形區(qū)域縮放到整個(gè)scrollView當(dāng)前可視的frame里面。如果截取的區(qū)域大于scrollView的frame時(shí)擎颖,圖片縮小榛斯,如果截取區(qū)域小于frame观游,會(huì)看到圖片放大。一般情況下rect需要自己計(jì)算出來(lái)肖抱。即要把用戶點(diǎn)擊坐標(biāo)附近的區(qū)域內(nèi)容在scrollViewl里進(jìn)行縮放备典。

拖拽手勢(shì)

最初,向上滑動(dòng)時(shí)意述,不響應(yīng)手勢(shì)提佣;

//MARK: 對(duì)pan手勢(shì)的處理
extension BrowseCollectionViewCell: UIGestureRecognizerDelegate{
    override func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool {
        guard let pan = gestureRecognizer as? UIPanGestureRecognizer else{
            return true
        }
        //在指定視圖的坐標(biāo)系中平移手勢(shì)的速度。
        let velocity = pan.velocity(in: self)
        //向上滑動(dòng)荤崇,不響應(yīng)手勢(shì)
        if velocity.y < 0 {
            return false
        }
        //橫向滑動(dòng)時(shí)拌屏,不響應(yīng)Pan手勢(shì)
        if abs(Int(velocity.x)) > Int(velocity.y){
            return false
        }
        //向下滑動(dòng),如果圖片頂部超出可視范圍术荤,不響應(yīng)
        if scrollView.contentOffset.y > 0 {
            return false
        }
        return true
    }
}

根據(jù)手勢(shì)的狀態(tài)倚喂,決定圖片的狀態(tài)

@objc fileprivate func panPhotoBrowser(_ pan:UIPanGestureRecognizer){
        guard imageView.image != nil else {
            return
        }
        switch pan.state {
        case .began:
            beganFrame = imageView.frame
            beganTouch = pan.location(in: scrollView)
        case .changed:
            //隨著收拾的移動(dòng),計(jì)算imageView和背景的alpha
            //返回圖片的frame和scale
            let result = panResult(pan)
            imageView.frame = result.0
            let alphaz: CGFloat = result.1 * result.1
            self.superview?.alpha = alphaz
        case .ended, .cancelled:
            imageView.frame = panResult(pan).0
            if pan.velocity(in: self).y > 0 {
                delegate?.photoBrowserCellImageClick()
            } else {
                // 取消dismiss
                endPan()
            }
        default:
            endPan()
        }
    }
/// 返回拖拽的結(jié)果(包括:image的frame和透明度)
    private func panResult(_ pan: UIPanGestureRecognizer) -> (CGRect, CGFloat) {
        
        //表示拖拽點(diǎn)在scrollView中的位置瓣戚,即拖拽的位置
        let currentTouch = pan.location(in: scrollView)
        
//        print(currentTouch)
        // 拖動(dòng)偏移量(距離)
        //在指定視圖的坐標(biāo)系中平移手勢(shì)的轉(zhuǎn)換端圈。
        //x和y值表示隨時(shí)間推移的總平移量。它們不是上次報(bào)告轉(zhuǎn)換時(shí)的delta值子库。在首次識(shí)別手勢(shì)時(shí)舱权,將轉(zhuǎn)換值應(yīng)用于視圖的狀態(tài)——不要在每次調(diào)用處理程序時(shí)將值連接起來(lái)。
        let translation = pan.translation(in: scrollView)
//        print("This is a test\(translation)")
        
        // 由下拉的偏移值決定縮放比例仑嗅,越往下偏移宴倍,縮得越小。scale值區(qū)間[0.3, 1.0]
        let scale = min(1.0, max(0.3, 1 - translation.y / bounds.height))
        
        let width = beganFrame.size.width * scale
        let height = beganFrame.size.height * scale
        
        // 計(jì)算x和y仓技。保持手指在圖片上的相對(duì)位置不變鸵贬。
        let xRate = (beganTouch.x - beganFrame.origin.x) / beganFrame.size.width
        let currentTouchDeltaX = xRate * width
        let x = currentTouch.x - currentTouchDeltaX
        
        let yRate = (beganTouch.y - beganFrame.origin.y) / beganFrame.size.height
        let currentTouchDeltaY = yRate * height
        let y = currentTouch.y - currentTouchDeltaY
        
        return (CGRect(x: x.isNaN ? 0 : x, y: y.isNaN ? 0 : y, width: width, height: height), scale)
    }

有啥疑問(wèn),一起探討脖捻,先寫到這~~~

DEMO地址(希望star)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末阔逼,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子地沮,更是在濱河造成了極大的恐慌颜价,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,311評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件诉濒,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡夕春,警方通過(guò)查閱死者的電腦和手機(jī)未荒,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,339評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)及志,“玉大人片排,你說(shuō)我怎么就攤上這事寨腔。” “怎么了率寡?”我有些...
    開(kāi)封第一講書人閱讀 152,671評(píng)論 0 342
  • 文/不壞的土叔 我叫張陵迫卢,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我冶共,道長(zhǎng)乾蛤,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書人閱讀 55,252評(píng)論 1 279
  • 正文 為了忘掉前任捅僵,我火速辦了婚禮家卖,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘庙楚。我一直安慰自己上荡,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,253評(píng)論 5 371
  • 文/花漫 我一把揭開(kāi)白布馒闷。 她就那樣靜靜地躺著酪捡,像睡著了一般。 火紅的嫁衣襯著肌膚如雪纳账。 梳的紋絲不亂的頭發(fā)上逛薇,一...
    開(kāi)封第一講書人閱讀 49,031評(píng)論 1 285
  • 那天,我揣著相機(jī)與錄音塞祈,去河邊找鬼金刁。 笑死,一個(gè)胖子當(dāng)著我的面吹牛议薪,可吹牛的內(nèi)容都是我干的尤蛮。 我是一名探鬼主播,決...
    沈念sama閱讀 38,340評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼斯议,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼产捞!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起哼御,我...
    開(kāi)封第一講書人閱讀 36,973評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤坯临,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后恋昼,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體看靠,經(jīng)...
    沈念sama閱讀 43,466評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,937評(píng)論 2 323
  • 正文 我和宋清朗相戀三年液肌,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了挟炬。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,039評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖谤祖,靈堂內(nèi)的尸體忽然破棺而出婿滓,到底是詐尸還是另有隱情,我是刑警寧澤粥喜,帶...
    沈念sama閱讀 33,701評(píng)論 4 323
  • 正文 年R本政府宣布凸主,位于F島的核電站,受9級(jí)特大地震影響额湘,放射性物質(zhì)發(fā)生泄漏卿吐。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,254評(píng)論 3 307
  • 文/蒙蒙 一缩挑、第九天 我趴在偏房一處隱蔽的房頂上張望但两。 院中可真熱鬧,春花似錦供置、人聲如沸谨湘。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 30,259評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)紧阔。三九已至,卻和暖如春续担,著一層夾襖步出監(jiān)牢的瞬間擅耽,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 31,485評(píng)論 1 262
  • 我被黑心中介騙來(lái)泰國(guó)打工物遇, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留乖仇,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,497評(píng)論 2 354
  • 正文 我出身青樓询兴,卻偏偏與公主長(zhǎng)得像乃沙,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子诗舰,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,786評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容