iOS開發(fā)——仿微信圖片瀏覽交互的實(shí)現(xiàn)(向下拖拽圖片退出圖片瀏覽器)

DEMO的github地址:https://github.com/YYProgrammer/YYPhotoBrowserLikeWX

效果如下圖

效果圖.gif
  • 實(shí)現(xiàn)圖片組的瀏覽奠骄,包含捏合縮放、雙擊縮放、單擊退出、向下拖拽退出等寒砖。
  • 重點(diǎn)是“向下拖拽退出”的實(shí)現(xiàn)阅畴。

架構(gòu)設(shè)計(jì)

下文稱下圖中左邊的界面為界面A定页,右邊為界面B


A-B.jpeg

界面A只是一個(gè)用來測試的界面踢械,界面B才是圖片瀏覽器拙泽,架構(gòu)設(shè)計(jì)主要針對界面B。主要需要考慮以下幾個(gè)點(diǎn):

  • 界面B的結(jié)構(gòu):N張圖片需要左右滑動(dòng)裸燎、圖片本身需要縮放顾瞻、N種手勢交互、后期額外控件的添加等德绿。
  • A到B荷荤、B到A的轉(zhuǎn)場動(dòng)畫。
  • 重點(diǎn):向下拖拽的交互怎么實(shí)現(xiàn)
界面B的結(jié)構(gòu)
  • N張圖片需要左右滑動(dòng):必然需要UIScrollView或其子類(UICollectionView)移稳,來放所有圖片蕴纳。
  • 圖片本身需要縮放:所以圖片本身需要一個(gè)UIScrollView包裝起來用于做縮放。
  • N種手勢交互:UIScrollView本身帶有很多手勢个粱,再往上添加手勢不妥古毛,所以應(yīng)當(dāng)創(chuàng)建個(gè)UIView,UIView里有UIScrollView都许,手勢加在UIView上稻薇,例如單擊、雙擊等胶征。
  • 后期額外控件的添加:


    額外控件.jpeg

    例如上圖紅圈的控件塞椎,明顯不能添加到UIScrollView中否則就跟著滑走了。

最終設(shè)計(jì)如下圖:


結(jié)構(gòu)示意.jpeg
  • 藍(lán)色是個(gè)scrollview睛低,里面放圖片案狠,這樣可以縮放圖片。(demo中我把它封裝成了YYPhotoBrowserSubScrollView)
  • 綠色是個(gè)scrollview钱雷,里面放藍(lán)色scrollview骂铁,這樣可以實(shí)現(xiàn)左右滑動(dòng)翻頁。(demo中我把它封裝成了YYPhotoBrowserMainScrollView)
  • 紅色是個(gè)控制器的view罩抗,這樣做可以隨意添加額外控件拉庵,而且控制器的話,也方便做界面A-界面B的轉(zhuǎn)場動(dòng)畫(demo中對應(yīng)YYPhotoBrowserViewController)澄暮。
  • 消息傳遞我采用的代理模式名段。
轉(zhuǎn)場動(dòng)畫

這里B是modal出的控制器,我把轉(zhuǎn)場效果交給了轉(zhuǎn)場代理transitioningDelegate泣懊,轉(zhuǎn)場動(dòng)畫具體做法可以參考文章:http://www.reibang.com/p/a65d3463f4bc
值得一提的是伸辟,拖拽時(shí)背景顏色透明,出現(xiàn)A的界面馍刮。換句話說信夫,B被present之后,A并沒有消失。這需要設(shè)置一個(gè)屬性

B.modalPresentationStyle = UIModalPresentationOverCurrentContext;

重點(diǎn):向下拖拽的交互的實(shí)現(xiàn)

控件簡介

結(jié)合上圖静稻,拖拽事件發(fā)生在藍(lán)色這一層警没。
藍(lán)色是個(gè)UIView(YYPhotoBrowserSubScrollView),添加了子控件UIScrollview(demo中對象命名為mainScrollView)振湾,mainScrollView的寬高占滿了YYPhotoBrowserSubScrollView杀迹。mainScrollView中有UIImageView。
雙擊手勢添加給YYPhotoBrowserSubScrollView押搪,雙擊后改變mainScrollView的zoomScale(縮放比例系數(shù))來實(shí)現(xiàn)縮放树酪,單擊手勢也添加給YYPhotoBrowserSubScrollView,單擊后通知代理-綠色控件大州,綠色再通知紅色控制器续语,控制器退回。

需求介紹

用戶向上拖拽時(shí)厦画,圖片向上移動(dòng)(即正常的scrollview的滾動(dòng)效果疮茄,結(jié)合demo中第一張長圖片查看)。
向下拖拽到最頂部并持續(xù)向下拖拽時(shí)根暑,圖片遂手勢移動(dòng)力试,并變小,背景逐漸透明购裙。松手瞬間懂版,如果手勢是向下移動(dòng)的,B頁退出躏率,如果手勢是向上移動(dòng)的,圖片回到原來的位置

解決方案分析

那么問題來了民鼓,拖拽的交互理所當(dāng)然動(dòng)用手勢-UIPanGestureRecognizer薇芝,添加給誰呢?

首先我們來看一下一個(gè)手勢發(fā)生時(shí)丰嘉,發(fā)生了哪些事情:
1夯到、生成一個(gè)UIEvent事件;
2饮亏、通過事件響應(yīng)鏈查找最合適的事件執(zhí)行者耍贾;
3、調(diào)用事件執(zhí)行者綁定的手勢事件路幸。
所以荐开,手勢所綁定的事件在執(zhí)行前,會(huì)先通過逐級查找的方式简肴,找到最適合響應(yīng)手勢的控件晃听,再執(zhí)行其方法,流程如下圖(白色原點(diǎn)處發(fā)生點(diǎn)擊)


事件響應(yīng)鏈.jpeg

圖中控件與上面的結(jié)構(gòu)圖的控件一致,藍(lán)色線是向下詢問過程能扒,橙色是返回結(jié)果的過程佣渴。
詢問過程:
1、application:window你好初斑,我收到一個(gè)事件(UIEvent)辛润,是在點(diǎn)你身上的,你看一看具體是你哪個(gè)孩子(subview)來響應(yīng)见秤,問好了告訴我砂竖。
2、window:我確認(rèn)了一下秦叛,我是可見的(hidden != NO && alpha >= 0.01)晦溪,我是可點(diǎn)擊的(userInteractionEnabled != NO),而且點(diǎn)擊的點(diǎn)確實(shí)在我身上([self pointInside:point withEvent:event] == YES)挣跋,那么三圆,我來遍歷一下我的孩子們(subview),看看誰最合適避咆,如果沒有的話舟肉,那就是我了
3查库、4路媚、5、同上樊销。
返回過程:
1整慎、UIImageView:我不能被點(diǎn)擊。
2围苫、UIScrollview:我的孩子都不能響應(yīng)裤园,那就是我了。
3剂府、YYPhotoBrowserMainScrollView:UIScrollview可以拧揽。
4、5腺占、同上淤袜。
最后藍(lán)色那個(gè)UIScrollview就成了事件的響應(yīng)者。
其實(shí)這個(gè)詢問和返回的過程衰伯,就是UIView里的方法

- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event

按系統(tǒng)流程重寫的話铡羡,內(nèi)部過程如下:

- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{
    // 1.判斷當(dāng)前控件能否接收事件
    if (self.userInteractionEnabled == NO || self.hidden == YES || self.alpha <= 0.01) return nil;
    
    // 2. 判斷點(diǎn)在不在當(dāng)前控件
    if ([self pointInside:point withEvent:event] == NO) return nil;
    
    // 3.從后往前遍歷自己的子控件(后添加的視圖在上面,在上面優(yōu)先響應(yīng))
    NSInteger count = self.subviews.count;
    for (NSInteger i = count - 1; i >= 0; i--)
    {
        UIView *childView = self.subviews[i];
        //把當(dāng)前控件上的坐標(biāo)系轉(zhuǎn)換成子控件上的坐標(biāo)系
        CGPoint childP = [self convertPoint:point toView:childView];
        //讓subview繼續(xù)找它的subview
        UIView *fitView = [childView hitTest:childP withEvent:event];
        if (fitView)//尋找到最合適的view
        {
            return fitView;
        }
    }
    
    // 循環(huán)結(jié)束,表示沒有比自己更合適的view
    return self;
}

然后嚎研,再調(diào)用響應(yīng)者綁定的對應(yīng)方法去執(zhí)行蓖墅,并且在調(diào)用時(shí)會(huì)將本次觸摸相關(guān)的等信息裝進(jìn)UIGestureRecognizer里作為參數(shù)库倘。

現(xiàn)在,我們來看一下這幾個(gè)不可行的方案:

  • 添加一個(gè)pan手勢給UIImageView论矾,在手勢事件中判斷手勢方向教翩,如果使向上拖拽,把事件傳遞給scrollview贪壳,如果是向下饱亿,做“向下拖拽退出”交互效果。
    • 不可行原因:
      要判斷手勢方向闰靴,只能在手勢事件中進(jìn)行(hitTest中無法通過攜帶的UIEvent參數(shù)判斷方向)彪笼,通過方向來改變響應(yīng)者,而要改變手勢響應(yīng)者蚂且,只能在hitTest方法中配猫,但hitTest是在手勢事件執(zhí)行前,所以已經(jīng)確定了UIImageView為響應(yīng)者杏死,再想改變響應(yīng)者泵肄,UIImageView就會(huì)中斷對事件的響應(yīng),本次觸摸事件就宣告結(jié)束淑翼,需要再次抬起手指腐巢,重新下拉,生成新的事件玄括,這個(gè)時(shí)候才能讓scrollview來響應(yīng)冯丙。
  • 重寫scroolview的pan手勢的綁定事件。如果是向上拖動(dòng)遭京,就按系統(tǒng)的交互寫胃惜,如果是向下,就做“向下拖拽退出”交互效果哪雕。
    無論是新建一個(gè)pan手勢覆蓋掉scroolview自帶的蛹疯,還是找到它自帶的pan手勢打印出它綁定的方法(沒記錯(cuò)的話叫handlePan:)然后重寫,還是自己用UIView的子類實(shí)現(xiàn)一個(gè)自定義Scrollview热监,都算作重寫,因?yàn)槎家约簩?shí)現(xiàn)系統(tǒng)的交互效果饮寞。
    • 不可行原因:
      理論上不是不可行孝扛,是非常難。因?yàn)橄到y(tǒng)交互上幽崩,不只是單純的手指移動(dòng)苦始,內(nèi)容跟著動(dòng),還有:例如慌申,快速滑動(dòng)頁面后松手陌选,頁面會(huì)持續(xù)滑動(dòng)并以一個(gè)加速度做系數(shù)來減速直到停止理郑,這個(gè)加速度怎么算?又如:內(nèi)容滾到頂部后持續(xù)下拉再松手咨油,會(huì)像彈簧一樣彈回去您炉,回去的速度是變化的,這里的加速度又是多少役电?
可行方案

思考題做到這里赚爵,其實(shí)答案已經(jīng)很接近了。
所有拖動(dòng)的交互都離不開pan手勢UIPanGestureRecognizer法瑟,所以UIScrollview既然能在手指移動(dòng)時(shí)做事兒冀膝,那它也離不開UIPanGestureRecognizer。
翻看UIScrollview的.h文件不難發(fā)現(xiàn)霎挟,它其實(shí)已經(jīng)暴露了它的pan手勢(不暴露就runtime遍歷屬性窝剖,總能找到想要的)。

h文件.jpeg

上文說到酥夭,調(diào)用手勢綁定事件時(shí)赐纱,會(huì)把觸摸相關(guān)的信息放進(jìn)手勢對象里,所以手指在scrollview里移動(dòng)時(shí)采郎,就能從它的panGestureRecognizer拿到我們想要的信息:手指移動(dòng)路徑千所,就能根據(jù)這些數(shù)據(jù),做想要的效果蒜埋。
所以方案如下:當(dāng)scrollview在頂部并向下拖拽時(shí)淫痰,隱藏scrollview中本來的UIImageview,造一個(gè)一模一樣的用來移動(dòng)的imageview整份。獲取到scrollview的panGestureRecognizer的手指位置信息待错,移動(dòng)并縮放圖片,通知代理設(shè)置控制器背景透明度烈评。手指松開時(shí)火俄,根據(jù)手指瞬間方向判斷是否需要退出頁面,并執(zhí)行相應(yīng)操作讲冠。

難點(diǎn)解決
  • 手勢綁定事件的方法在父類中瓜客,怎么實(shí)時(shí)監(jiān)控手勢發(fā)生并移動(dòng)了呢?怎么監(jiān)聽手勢結(jié)束呢竿开?
    當(dāng)然是用到UIScrollview的代理方法谱仪。
/** scrollview正在滾動(dòng) */
- (void)scrollViewDidScroll:(UIScrollView *)scrollView;   
/** scrollview即將結(jié)束拖拽 */
- (void)scrollViewWillEndDragging:(UIScrollView *)scrollView withVelocity:(CGPoint)velocity targetContentOffset:(inout CGPoint *)targetContentOffset;

這里需要注意的是,如果圖片沒有屏幕大否彩,那么scrollview的contentSize是小于frame的疯攒,這個(gè)時(shí)候并不能拖拽,代理方法自然也不執(zhí)行列荔。需要設(shè)置scrollview的屬性:

/** 總是有彈簧效果 */
_mainScrollView.alwaysBounceVertical = YES;
_mainScrollView.alwaysBounceHorizontal = YES;//這是為了左右滑時(shí)能夠及時(shí)回調(diào)scrollViewDidScroll代理

注意左右的彈簧屬性也要設(shè)置敬尺,否則向下拖動(dòng)交互進(jìn)行中枚尼,如果用戶開始左右拖動(dòng),而mainScrollView不能左右拖動(dòng)砂吞,那代理方法不會(huì)執(zhí)行署恍,會(huì)造成圖片卡住不動(dòng)的感覺。

  • 造跟著手指動(dòng)的imageView時(shí)呜舒,初始frame的計(jì)算
    其實(shí)不算很難锭汛,但是要場景要考慮全面,因?yàn)樵谕蟿?dòng)時(shí)袭蝗,圖片可能已經(jīng)是放大狀態(tài)唤殴。
- (void)saveFrameBeginPan
{
    imageWidthBeforeDrag = self.mainImageView.yy_width;//開始時(shí)的高
    imageHeightBeforeDrag = self.mainImageView.yy_height;//開始時(shí)的寬
    //計(jì)算圖片Y需要考慮到圖片此時(shí)的高,如果足夠高時(shí)到腥,交互發(fā)生時(shí)y一定是0
    CGFloat imageBeginY = (imageHeightBeforeDrag < kMainScreenHeight) ? (kMainScreenHeight - imageHeightBeforeDrag) * 0.5 : 0.0;
    imageYBeforeDrag = imageBeginY; //+ imageHeightBeforeDrag * 0.5;
    //centerX需要考慮到offset
    scrollOffsetX = self.mainScrollView.contentOffset.x;
    CGFloat imageX = -scrollOffsetX;
    imageCenterXBeforeDrag = imageX + imageWidthBeforeDrag * 0.5;
}

為什么是y值加centerX的值朵逝?
因?yàn)檫@樣圖片縮小的效果是往圖片最中間縮。

其它小細(xì)節(jié)
  • 雙手捏合來縮放圖片時(shí)乡范,也會(huì)調(diào)用代理scrollViewDidScroll
    解決:根據(jù)手勢的手指數(shù)>1判斷是否做交互配名,另外還可以設(shè)置一個(gè)變量來記錄是不是正在縮放。
  • 向下拖拽時(shí)向左右滑動(dòng)會(huì)出現(xiàn)上一張/下一張圖片的邊緣
    解決:拖拽時(shí)通知代理隱藏其它圖片晋辆,結(jié)束時(shí)顯示出來渠脉。
  • 手勢拖動(dòng)時(shí),其實(shí)scrollview正在被拖動(dòng)瓶佳,里面的圖片也就正在移動(dòng)芋膘,圖片彈回去時(shí),位置就變化了霸饲,會(huì)有一個(gè)瞬移的感覺
    解決:拖動(dòng)開始記錄下offset为朋,結(jié)束拖動(dòng)時(shí)賦值回去。
  • 松開時(shí)怎么判斷是否返回
    解決:在拖動(dòng)時(shí)記錄瞬間的方向厚脉,即如果當(dāng)前手勢點(diǎn)的y大于之前的习寸,解釋向下移動(dòng),松開就要退出頁面傻工。否則不退出頁面霞溪。甚至還可以加一個(gè),如果只像下拉了一丟丟中捆,就不退出頁面威鹿。
  • 當(dāng)scrollview的offset.y小于0時(shí)才執(zhí)行交互代碼,那如果向下拉了轨香,不松手又向上拉,此時(shí)offset.y就大于0了幼东,那不就不執(zhí)行代碼了臂容,圖片不就卡住不動(dòng)了嗎科雳?
    解決:設(shè)置變量來記錄是否正在下拉,拉動(dòng)開始是為yes脓杉,結(jié)束時(shí)為no糟秘,當(dāng)offset.y<0或者變量為yes,就執(zhí)行下拉球散。
  • 小細(xì)節(jié)太多了尿赚。。蕉堰。凌净。

實(shí)戰(zhàn)效果

項(xiàng)目效果.gif

其它

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末冰寻,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子皿渗,更是在濱河造成了極大的恐慌斩芭,老刑警劉巖,帶你破解...
    沈念sama閱讀 221,198評論 6 514
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件乐疆,死亡現(xiàn)場離奇詭異划乖,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)挤土,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,334評論 3 398
  • 文/潘曉璐 我一進(jìn)店門琴庵,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人耕挨,你說我怎么就攤上這事细卧。” “怎么了筒占?”我有些...
    開封第一講書人閱讀 167,643評論 0 360
  • 文/不壞的土叔 我叫張陵贪庙,是天一觀的道長。 經(jīng)常有香客問我翰苫,道長止邮,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,495評論 1 296
  • 正文 為了忘掉前任奏窑,我火速辦了婚禮导披,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘埃唯。我一直安慰自己撩匕,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,502評論 6 397
  • 文/花漫 我一把揭開白布墨叛。 她就那樣靜靜地躺著止毕,像睡著了一般模蜡。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上扁凛,一...
    開封第一講書人閱讀 52,156評論 1 308
  • 那天忍疾,我揣著相機(jī)與錄音,去河邊找鬼谨朝。 笑死卤妒,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的字币。 我是一名探鬼主播则披,決...
    沈念sama閱讀 40,743評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼纬朝!你這毒婦竟也來了收叶?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,659評論 0 276
  • 序言:老撾萬榮一對情侶失蹤共苛,失蹤者是張志新(化名)和其女友劉穎判没,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體隅茎,經(jīng)...
    沈念sama閱讀 46,200評論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡澄峰,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,282評論 3 340
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了辟犀。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片俏竞。...
    茶點(diǎn)故事閱讀 40,424評論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖堂竟,靈堂內(nèi)的尸體忽然破棺而出魂毁,到底是詐尸還是另有隱情,我是刑警寧澤出嘹,帶...
    沈念sama閱讀 36,107評論 5 349
  • 正文 年R本政府宣布席楚,位于F島的核電站,受9級特大地震影響税稼,放射性物質(zhì)發(fā)生泄漏烦秩。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,789評論 3 333
  • 文/蒙蒙 一郎仆、第九天 我趴在偏房一處隱蔽的房頂上張望只祠。 院中可真熱鬧,春花似錦扰肌、人聲如沸抛寝。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,264評論 0 23
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽墩剖。三九已至猴凹,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間岭皂,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,390評論 1 271
  • 我被黑心中介騙來泰國打工沼头, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留爷绘,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,798評論 3 376
  • 正文 我出身青樓进倍,卻偏偏與公主長得像土至,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子猾昆,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,435評論 2 359

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