iOS開發(fā)實現(xiàn)類似B站豎屏視頻的拖動效果

最近嘗試模仿實現(xiàn)B站的豎屏視頻的拖動效果,實現(xiàn)的最終效果圖如下:


最終效果圖.gif

(視頻有最大尺寸和最小尺寸限制,通過滑動UITableView來動態(tài)更改視頻的高度)

github上Demo地址
相應的實現(xiàn)文件名稱為:PullAndScrollViewController

項目開始前需要注意的點

在做這個項目時遇到了一些坑冰寻,在這里分享一下

使用Masonry.h進行view的初始化布局,之后在viewDidLayoutSubViews或者按鈕的實現(xiàn)方法中改變view的frame缀壤,主要是改變高度

會發(fā)現(xiàn)猪杭,不論怎么寫餐塘,界面上view的大小都不發(fā)生變化
但是使用RacObserve監(jiān)聽view的frame屬性,就會發(fā)現(xiàn)皂吮,其實view的frame已經(jīng)發(fā)生了變化

但是在界面上表現(xiàn)不出來
甚至在更改frame的大小后加上強制刷新的代碼戒傻,界面上的表現(xiàn)依舊沒什么反應

//強制刷新代碼
[self.view setNeedsLayout];
[self.view layoutifNeeded];

后來發(fā)現(xiàn),如果初始使用masonry布局進行約束蜂筹,那么之后更改的話需纳,同樣需要使用masonry布局約束進行更改,這樣可以很好的達到效果

如果前面布局使用frame直接布局艺挪,那么后面不論是更改frame還是通過masonry更改約束都能實現(xiàn)相應的效果

具體的原因我還沒有確定不翩,通過查詢資料發(fā)現(xiàn):
參考鏈接:https://www.sohu.com/a/195141167_163917
該文章中有提到:

首先你要知道autolayout和frame的關系,autolayout最終也是轉(zhuǎn)成frame,masonry是建立在autolayout之上的口蝠。你沒獲取到正確的值器钟,那是因為約束還沒布局完成。相當于就是我們給一定的約束亚皂,系統(tǒng)內(nèi)部自己去根據(jù)約束條件轉(zhuǎn)成對應的frame俱箱,而這需要一個過程。想要拿到正確的frame最好的就是讓autolayout完成之后灭必,什么時候完成呢?那就是在layoutsubviews for view or didlayoutsubviews for controller 里獲取,當然在控制器的viewdidappear里也拿得到乃摹,但是正確做法和最佳做法還是在控制器里的viewdidlayout里獲取最好~因為autolayout會根據(jù)約束禁漓,不停的去改變frame,這方法里最后拿到的frame就是最終姿勢.

意思就是masonry布局的并不能馬上獲取到frame的高度大小孵睬,autolayout轉(zhuǎn)化為frame需要一定的時間播歼,或許是因為使用masonry布局的,后續(xù)使用frame直接更改會出現(xiàn)一些問題

之后掰读,去查看了masonry在github上的庫秘狞,在其中的issue中看到了相同的提問


image.png

可惜,并沒有進行解答
等后面找到相應的解答之后再更新在這里

項目中TestViewController就是為了驗證這個問題所寫的測試文件蹈集,其中使用#import <ReactiveObjC/ReactiveObjC.h>來對myView的frame屬性進行監(jiān)聽
有興趣的可以看看

具體的實現(xiàn)步驟

具體的實現(xiàn)文件為pullAndScrollViewConroller
在.h中定義相關的屬性

@property (nonatomic, strong) UIView *myView;
@property (nonatomic, assign) CGFloat maxViewHeight;//最大高度
@property (nonatomic, assign) CGFloat minViewHeight;//最小高度

@property (nonatomic, strong) UITableView *tableView;
@property (nonatomic, assign) CGPoint scrollBeginDraggingOffset;

之后在.m中實現(xiàn)初始的基本的界面以及懶加載

- (void)viewDidLoad {
    [super viewDidLoad];
    
    self.view.backgroundColor = [UIColor whiteColor];
    self.navigationController.navigationBar.translucent = NO;
    self.title = @"pull scrollView Demo  使用frame來改變";
    
    //初始化高度
    self.minViewHeight = 200;
    self.maxViewHeight = 400;
    
    [self.view addSubview:self.myView];
    [self.view addSubview:self.tableView];
    //這個方法主要為了查看過程中一些屬性的變化烁试,在使用時可以將其注釋掉
    [self addObserve];
}

- (void)addObserve {
    
    typeof(self) __weak weakSelf = self;
    [RACObserve(self.myView, frame) subscribeNext:^(id  _Nullable x) {
        typeof(weakSelf) __strong self = weakSelf;
        NSLog(@"--------------");
        NSLog(@"height高度發(fā)生了變化%f",self.myView.frame.size.height);
    }];
    [RACObserve(self, scrollBeginDraggingOffset) subscribeNext:^(id  _Nullable x) {
        typeof(weakSelf) __strong self = weakSelf;
        NSLog(@"1111111111111111");
        NSLog(@"scrollBeginDraggingOffSet發(fā)生了變化%f",self.scrollBeginDraggingOffset.y);
    }];
    [RACObserve(self.tableView, contentOffset) subscribeNext:^(id  _Nullable x) {
        typeof(weakSelf) __strong self = weakSelf;
        NSLog(@"222222222222222");
        NSLog(@"contentOffsetY發(fā)生了變化%f",self.tableView.contentOffset.y);
    }];
}

- (void)viewDidLayoutSubviews {
    [super viewDidLayoutSubviews];

    self.tableView.frame = CGRectMake(0, CGRectGetMaxY(self.myView.frame), self.view.bounds.size.height, self.view.bounds.size.height - CGRectGetMaxY(self.myView.frame));
}

相應的懶加載為

#pragma mark - lazy load
- (UIView *)myView {
    if (_myView) {
        return _myView;
    }
    _myView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, self.view.bounds.size.width, 400)];
    _myView.backgroundColor = [UIColor yellowColor];
    return _myView;
}

- (UITableView *)tableView {
    if (_tableView) {
        return _tableView;
    }
    _tableView = [[UITableView alloc] initWithFrame:CGRectMake(0, 400, self.view.bounds.size.width, 200) style:UITableViewStylePlain];
    _tableView.backgroundColor = [UIColor clearColor];
    _tableView.showsVerticalScrollIndicator = YES;
    _tableView.delegate = self;
    _tableView.dataSource = self;
    [_tableView registerClass:[UITableViewCell class] forCellReuseIdentifier:@"UITableViewCell"];
    return _tableView;
}

實現(xiàn)UITableView的delegate/datasource協(xié)議

#pragma mark - UITableViewDelegate/DataSource
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
    return 1;
}

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
    return 100;
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"UITableViewCell"];
    cell.textLabel.text = [NSString stringWithFormat:@"第%ld個cell",(long)indexPath.row];
    return cell;
}

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
    [tableView deselectRowAtIndexPath:indexPath animated:YES];
    NSLog(@"點擊了第%ld個cell",(long)indexPath.row);
}

- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
    return 44;
}

這樣基金的界面就已經(jīng)寫好了,運行后的效果為:


image.png

此時滑動的話拢肆,上方黃色的UIView不會更換大小
為了達到我們最初的效果减响,我們的思路是在滑動的時候根據(jù)UITableView的contentOffset.y的大小與視頻高度的比較判斷來設置UITableView的偏移量

以此達到我們的效果
在viewDidLayoutSubViews中,我們設置了UITableView的頂部與myView的底部緊挨著

UITableView的滑動調(diào)用的就是UIScrollViewDelegate郭怪,前面有一篇文章專門寫了UIScrollViewDelegate中各個協(xié)議方法的調(diào)用順序支示。
ScrollView滑動協(xié)議方法探究

主要的就是在ScrollViewDidScroll協(xié)議方法中進行相應的邏輯處理

- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
  //獲取UITableView的偏移量
   CGFloat offsetY = scrollView.contentOffset.y;
  //計算UITableView的最大偏移量
   CGFloat maxOffsetY = scrollView.contentSize.height - 
   scrollView.contentInset.top - scrollView.contentInset.bottom - 
   scrollView.frame.size.height;
  if (offsetY > 0) {
        NSLog(@"向上滑動offsetY為正值,值的大小為%f",offsetY);
    } else {
        NSLog(@"向下滑動offsetY為負值,值的大小為%f",offsetY);
    }
    CGFloat height = self.myView.bounds.size.height;
    CGFloat currentHeight = self.myView.bounds.size.height;
    //根據(jù)當前view的高度判斷,是否處在maxViewHeight和minViewHeight之間鄙才,如果處在之間颂鸿,需要修改view的高度,不需要改變UITableView的contentOffset
 //下面的邏輯就是處在最大高度和最小高度之間攒庵,偏移多少嘴纺,就修改高度多少,這樣UITableView就不需要改變contentOffsetY
  if (offsetY > 0) {
        //表示向上滑動
        if (currentHeight > self.minViewHeight) {
            height = height - offsetY;
        }
    } else {
        //表示向下滑動
        if (currentHeight < self.maxViewHeight) {
            height = height - offsetY;
        }
    }
   //判斷height在減去offsetY之后的高度是否還處于maxViewHeight和minViewHeight之間
   if (height < self.minViewHeight) {
        height = self.minViewHeight;
    } else if (height > self.maxViewHeight) {
        height = self.maxViewHeight;
    }
//當height的高度不等于currentHeight時叙甸,說明view的height發(fā)生了變化颖医,需要修改view的frame的大小,UITableView的不需要再添加代碼修改裆蒸,UITableView的frame修改我們一直放在了viewDidLoadLayoutSubViews中
if (height != currentHeight) {
        self.myView.frame = CGRectMake(0, CGRectGetMinY(self.myView.frame), CGRectGetWidth(self.view.frame), height);
        [self.view setNeedsLayout];
    }
}

這樣的話熔萧,相應的邏輯基本上就實現(xiàn)了,但是運行之后,看到效果并不如我們所想的那樣
這樣運行的效果圖為:


初步效果圖.gif

從圖中可以看出佛致,view的高度變化總是快速變化贮缕,和我們預期的想法不一致

后面使用RACObserve監(jiān)聽UITableView的contentOffset屬性

[RACObserve(self.tableView, contentOffset) subscribeNext:^(id  _Nullable x) {
        typeof(weakSelf) __strong self = weakSelf;
        NSLog(@"222222222222222");
        NSLog(@"contentOffsetY發(fā)生了變化%f",self.tableView.contentOffset.y);
    }];

經(jīng)過調(diào)試發(fā)現(xiàn)了邏輯上的漏洞

首先需要明確一點,對于UITableView俺榆,如果改變它的frame的位置感昼,比如向上移動100,它的contentOffsyY會保持原狀罐脊,不會發(fā)生變化
但是如果通過滑動來改變位置的話定嗓,contentOffsetY會發(fā)生一些變化
這部分可以通過自己編寫例子驗證,在Test2ViewController中我進行的這個驗證
因為只要滑動萍桌,contentOffsetY就會有變化

上面的邏輯漏洞也就不難發(fā)現(xiàn)宵溅,在

if (height != currentHeight) {
        self.myView.frame = CGRectMake(0, CGRectGetMinY(self.myView.frame), CGRectGetWidth(self.view.frame), height);
        [self.view setNeedsLayout];
    }

這里,我們修改myView的frame之后上炎,viewDidLayoutSubViews中會跟著修改UITableView的frame恃逻,這個過程中按照我們的設想,contentOffsetY不應該發(fā)生變化藕施,甚至在滑動的過程中寇损,修改的都是view的height高度,不應該改變contentOffstY

所以裳食,最直接的就是記錄下最初滑動前UITableView的contentOffsetY矛市,之后在改變myView的frame之后,立馬使用setContentOffset設置UITableView的偏移量和滑動前相同即可

記錄滑動前的偏移量胞谈,我們可以在- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView這個方法中國呢記錄

- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView {
    NSLog(@"scrollViewWillBeginDragging...");
    CGPoint p = scrollView.contentOffset;
    CGFloat maxOffsetY = scrollView.contentSize.height - scrollView.contentInset.bottom - scrollView.contentInset.top - scrollView.frame.size.height;
    if (p.y >= maxOffsetY) {
        p.y = maxOffsetY;
    }
    self.scrollBeginDraggingOffset = p;
}

之后尘盼,scrollViewDidSCroll中的邏輯需要添加以下代碼

CGFloat originOffsetY = MAX(0, self.scrollBeginDraggingOffset.y);
offsetY = MIN(offsetY, maxOffsetY) - originOffsetY;
其他的相同
if (height != currentHeight) {
        self.myView.frame = CGRectMake(0, CGRectGetMinY(self.myView.frame), CGRectGetWidth(self.view.frame), height);
        //加一句這個代碼
        [scrollView setContentOffset:CGPointMake(0, originOffsetY)];
        [self.view setNeedsLayout];
    }

這樣運行后,最終的效果圖


動態(tài)改變視頻大小.gif

和我們預期的結果一致

總結

github上Demo地址
相應的實現(xiàn)文件名稱為:PullAndScrollViewController

動態(tài)改變視頻大小.gif
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末烦绳,一起剝皮案震驚了整個濱河市卿捎,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌径密,老刑警劉巖午阵,帶你破解...
    沈念sama閱讀 219,539評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異享扔,居然都是意外死亡底桂,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,594評論 3 396
  • 文/潘曉璐 我一進店門惧眠,熙熙樓的掌柜王于貴愁眉苦臉地迎上來籽懦,“玉大人,你說我怎么就攤上這事氛魁∧核常” “怎么了厅篓?”我有些...
    開封第一講書人閱讀 165,871評論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長捶码。 經(jīng)常有香客問我羽氮,道長,這世上最難降的妖魔是什么惫恼? 我笑而不...
    開封第一講書人閱讀 58,963評論 1 295
  • 正文 為了忘掉前任档押,我火速辦了婚禮,結果婚禮上祈纯,老公的妹妹穿的比我還像新娘令宿。我一直安慰自己,他們只是感情好盆繁,可當我...
    茶點故事閱讀 67,984評論 6 393
  • 文/花漫 我一把揭開白布掀淘。 她就那樣靜靜地躺著,像睡著了一般油昂。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上倾贰,一...
    開封第一講書人閱讀 51,763評論 1 307
  • 那天冕碟,我揣著相機與錄音,去河邊找鬼匆浙。 笑死安寺,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的首尼。 我是一名探鬼主播挑庶,決...
    沈念sama閱讀 40,468評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼软能!你這毒婦竟也來了迎捺?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,357評論 0 276
  • 序言:老撾萬榮一對情侶失蹤查排,失蹤者是張志新(化名)和其女友劉穎凳枝,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體跋核,經(jīng)...
    沈念sama閱讀 45,850評論 1 317
  • 正文 獨居荒郊野嶺守林人離奇死亡岖瑰,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,002評論 3 338
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了砂代。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片蹋订。...
    茶點故事閱讀 40,144評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖刻伊,靈堂內(nèi)的尸體忽然破棺而出露戒,到底是詐尸還是另有隱情椒功,我是刑警寧澤,帶...
    沈念sama閱讀 35,823評論 5 346
  • 正文 年R本政府宣布玫锋,位于F島的核電站蛾茉,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏撩鹿。R本人自食惡果不足惜谦炬,卻給世界環(huán)境...
    茶點故事閱讀 41,483評論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望节沦。 院中可真熱鬧键思,春花似錦、人聲如沸甫贯。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,026評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽叫搁。三九已至赔桌,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間渴逻,已是汗流浹背疾党。 一陣腳步聲響...
    開封第一講書人閱讀 33,150評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留惨奕,地道東北人雪位。 一個月前我還...
    沈念sama閱讀 48,415評論 3 373
  • 正文 我出身青樓,卻偏偏與公主長得像梨撞,于是被迫代替她去往敵國和親雹洗。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 45,092評論 2 355

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