一、前言
1、寫了10多天的小程序代碼,有興趣的可以看我這篇小程序官方文檔-小程序版【持續更新】,被坑得有點暈,突然想換換口味,寫點iOS的,看群上有人提過這個拖拽view的功能,應該挺多人需要的,那就造一個分享吧。
2、公司有自己的一個直播項目,看其他直播app都有小屏幕可拖拽播放的view(如下圖),雖然還沒有這個需求,早點準備好。
3、而且很久沒分享了,像往常一樣,封裝輪子的過程都會詳細介紹,分享更多的都是封裝的思想。
-
4、來看看效果圖先吧:
演示效果圖,還可以吧
二、功能分析(針對所有控件)
1、可拖拽。最基本的功能,拖出父容器松手后可復位,可關閉。
2、拖拽不遮蓋。同級層次的控件,如果拖拽過程中出現重疊,會顯示在最上層。
3、可吸附邊界。吸附這點參考蘋果的AssistiveTouch,只能在屏幕的左邊或者右邊,可關閉。
4、彈簧效果。如果超出屏幕,會有彈簧效果,這點參考scrollView 的 bounces 效果,可關閉。
5、支持xib、storyboard 參數設置。很多控件都是在xib或storyboard 中創建,此時可通過,看下圖:
三、API 分析設計
- 1、是否允許拖拽,默認關閉,支持xib、storyboard 參數設置
/**
* @author gitKong
*
* 是否允許拖拽,默認關閉(可以在XIB或SB中設置)
*/
@property (nonatomic,assign)IBInspectable BOOL fl_canDrag;
- 2、是否需要邊界彈簧效果,默認開啟,支持xib、storyboard 參數設置
/**
* @author gitKong
*
* 是否需要邊界彈簧效果,默認開啟(可以在XIB或SB中設置)
*/
@property (nonatomic,assign)IBInspectable BOOL fl_bounces;
- 3、是否需要吸附邊界效果,默認開啟(可以在XIB或SB中設置
/**
* @author gitKong
*
* 是否需要吸附邊界效果,默認開啟(可以在XIB或SB中設置)
*/
@property (nonatomic,assign)IBInspectable BOOL fl_isAdsorb;
四、功能實現分析
-
1、分類或繼承實現。因為需要支持所有控件,我們都知道控件都是直接或者間接繼承
UIView
,此時可以有兩種辦法實現,繼承
和分類
,此時我是用分類實現的,至于為什么呢?(1)、如果我當前的view是已經繼承了另一個自定義view,而且功能很獨立,此時如果要給當前view添加拖拽功能的話,就必須融合兩個功能的代碼,這將變得很麻煩,而且會很亂。
(2)、如果我需要給一個按鈕添加拖拽功能,那么繼承就沒辦法實現了,因為你給這個按鈕修改了繼承,就沒了自帶的功能了。
-
2、監聽拖拽。拖拽事件可以通過
- (void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
方法去監聽,也可以通過添加 pan 拖拽手勢,至于為什么要用 手勢呢?- (1)、需要在分類中實現 touchesMoved 方法,此時如果外界也實現了這個方法,那么就很容易起沖突。
- (2)、雖然動態修改拖拽功能開關,但 會 多次調用 touchesMoved 方法,即使關閉了拖拽功能,相對手勢實現就沒那么靈活了。
3、Setter 方法里添加pan事件。一開始想到的是通過 runtime 的
Swizzle
替換掉 系統的init
方法,進而在自己的方法里面實現 拖拽手勢添加,但后來考慮到,此時是給UIView 添加分類,而頁面上的所有控件都是UIView 的 子類,此時 init 就會調用多次,會造成頁面顯示慢;于是直接在setter方法里面判斷,當需要拖拽的時候,我才添加事件,不需要拖拽的時候移除手勢,算是一個小優化。此時還需要記錄當前控件在父容器中的index
,這個等下就知道要用來干嘛。-
4、遮蓋處理。我們都知道,控件通過
addSubviews
添加到父容器中,是有順序的,后面添加的會在上層;此時如果我先添加 view1 在添加 view2,我拖拽 view1 到 view2 的時候,就會被 view2 擋住。做法如下:- (1)、遍歷父類view 的 subViews 數組,判斷是否與當前view重合,用系統自帶的
CGRectIntersectsRect
方法判斷。 - (2)、在拖拽開始(
UIGestureRecognizerStateBegan
)的時候,用bringSubviewToFront
將拖拽的view 移到最上層。 - (3)、在拖拽結束(
UIGestureRecognizerStateEnd
)的時候,判斷此時狀態是否還重合,如果不重合,通過insertSubview
將控件重置回原來的順序(通過上面第三點記錄的index
)
- (1)、遍歷父類view 的 subViews 數組,判斷是否與當前view重合,用系統自帶的
5、支持xib、storyboard 參數設置。這個只需要添加一個 關鍵字修飾就行
IBInspectable
,用法可以看我API 設計,這個主要作用是使view內的變量可視化,并且可以修改后馬上看到
6、彈簧效果和吸附效果。這個可以使用UIView 的 動畫就可以實現,此時有一個注意點,判斷吸附到左邊還是右邊,比較的應該是絕對位置,而不是相對位置,需要加上父類的x值
if (gesR.view.fl_centerX + self.superview.fl_x > self.superview.fl_centerX) {
[UIView animateWithDuration:0.25 animations:^{
gesR.view.center = CGPointMake(self.superview.fl_width - self.fl_width / 2, y);
}];
}
else{
[UIView animateWithDuration:0.25 animations:^{
gesR.view.center = CGPointMake(self.fl_width / 2, y);
}];
}
五、注意點:
- 1、如果你是使用約束來布局的話,此時可能出現當你拖拽出一定范圍后,能正常停留在當前位置;但當你此時拖拽后的位置在約束好的位置附近,就會被吸附回去,如下圖:(此時圖片是使用約束布局)
- 2、如果手勢失敗,會有斷言提示。
六、總結
-
1、為了方便大家使用,支持cocoapod,通過
pod search FLDragView
就能搜到。如果你搜不到,先清理一下吧,執行rm ~/Library/Caches/CocoaPods/search_index.json
執行完重新 search 就行cocoapod 2、代碼不難實現,具體代碼就不拷貝上來了,代碼中計算相對多一點,文字分析應該比較清楚了,如果還有什么其他問題,盡管找我。
3、Github地址 ,歡迎大家關注我,喜歡給個star 和 like ,你的支持是我最大的動力,會隨時更新原創文章喔!