ios實(shí)現(xiàn)底部PopupWindow的示例代碼(底部彈出菜單)
前言
在Android中要實(shí)現(xiàn)底部彈出菜單很容易,有專門的PopupWindow類,我們只需要用xml訂制好其內(nèi)容View以及設(shè)置其彈出位置即可,非常容易。但是,在ios中就不能這么直接了,沒有現(xiàn)成的東西,需要自己想辦法來實(shí)現(xiàn)。
思路分析
- 反正最終一定要實(shí)現(xiàn)效果,那么內(nèi)容View一定要解決掉,那么是在Interface Builder編輯實(shí)現(xiàn)還是直接用代碼實(shí)現(xiàn)呢?答案是都可以,但為了方便和訂制相對比較規(guī)范,建議用interface Builder編輯。
- 內(nèi)容ok了,那么內(nèi)容放在哪里?這是個核心問題,也就是確定PopupWindow的容器。我們知道ios視圖的層級結(jié)構(gòu)是Window->RootView->各種組件。顯然PopupWindow要么放在Window中要么放在RootView中,但是如果放在RootView勢必會影響RootView中原來的組件,而且與PopupWindow這個名字也不相符。所以,理想的容器就是Window。
- 如何彈出的問題,其實(shí)這個比較好解決,彈出時就把PopupWindow加入到容器,消失時就把它從容器中移除。要實(shí)現(xiàn)從底部彈出,從底部消失的效果,只需要借助UIView動畫,變換起始坐標(biāo)就可以了,比較容易。
具體實(shí)現(xiàn)
UI
用Interface Builder實(shí)現(xiàn),ViewController直接選用UIViewController,內(nèi)部選的是UICollectionView方便動態(tài)更新,當(dāng)然這個根據(jù)需要隨意。布局用AutoLayout就不用多說了,比較簡單。直接上圖:
注意此ViewController的RootView就是我們需要添加到Window的view,為了效果,將其背景色置為clearcolor。將其中交互的組件右鍵拖拽到PopupWindow類形成映射。
彈出
將RootView添加到Window中,并顯示在最前面。直接上代碼:
func create()-> PopupWindow { let window = UIApplication.shared.keyWindow window?.addSubview(self.view) window?.bringSubview(toFront: self.view) self.view.frame = CGRect(x: 0, y: UIScreen.main.bounds.height, width: UIScreen.main.bounds.width, height: UIScreen.main.bounds.height) UIView.animate(withDuration: 0.3) { animation in self.view.frame = CGRect(x: 0, y: 0, width: UIScreen.main.bounds.width, height: UIScreen.main.bounds.height) } return self }
這里關(guān)鍵就是addSubView方法添加到Window,bringSubView顯示到前臺。UIView動畫將view的y坐標(biāo)由屏幕高度改變?yōu)?,從而實(shí)現(xiàn)由底部彈出效果。
這里返回自身對象是為了方便鏈?zhǔn)皆O(shè)置組件屬性和其他屬性。
添加交互功能
雖然現(xiàn)在已經(jīng)可以彈出PopupWindow了,但并不具有交互功能。并且我們?yōu)榱吮阌趶?fù)用,不會把交互的功能直接寫在PopupWindow中,而是根據(jù)需要寫在調(diào)用它的地方。這里有兩種方式:
- 以協(xié)議的方式,把方法寫在協(xié)議中,調(diào)用部分實(shí)現(xiàn)這個協(xié)議并重寫回調(diào)函數(shù),這和Android的接口基本一致。
- 以函數(shù)作為參數(shù)類型的方式,調(diào)用部分通過傳遞函數(shù)類型參數(shù)至PopupWindow,而在調(diào)用部分以閉包或者尾隨閉包的形式添加交互功能。
兩種方式一般都可以隨性,但第一種適合交互函數(shù)比較多的時候。第二種適合于同一調(diào)用類中出現(xiàn)多個地方不同調(diào)用,一些設(shè)置屬性也不相同。
我們這里選擇第一種,以協(xié)議的方式:
protocol PopupWindowDelegate { func attach() func detach() func rename() func delete() func control() }
這里具體函數(shù)完全不用管它,是從項(xiàng)目中截取的。
當(dāng)然我們需要在PopupWindow中定義一個該協(xié)議類型的變量:
public var delegate: PopupWindowDelegate?
通過協(xié)議對象來調(diào)用交互函數(shù):
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { let row = indexPath.row switch itemsString[row] { case "attach": delegate?.attach() cancel() case "detach": delegate?.detach() cancel() case "rename": delegate?.rename() cancel() case "delete": delegate?.delete() cancel() case "control": delegate?.control() cancel() default: break } }
這是UICollectionView item的選擇函數(shù),這里不多說。注意協(xié)議對象對其函數(shù)的調(diào)用,這里只相當(dāng)于一種綁定。真正的調(diào)用在調(diào)用地方對協(xié)議對象的賦值。
除了這些還有一個最重要的東西,就是聲明對于PopupWindow對象的一個強(qiáng)引用,如果這個不存在,交互功能依然不可用。原因是為了防止當(dāng)前對象被回收掉,有了強(qiáng)引用,只有強(qiáng)引用置空時,對象才能被回收掉。
var strongSelf: PopupWindow?
引用賦值即可以放在彈出函數(shù)create()中,也可以放在viewDidLoad()中,執(zhí)行順序是彈出函數(shù)create()在前。這里放在viewDidLoad()中的:
override func viewDidLoad() { super.viewDidLoad() // Do any additional setup after loading the view. self.view.backgroundColor = UIColor.init(white: 0, alpha: 0) let gesture = UITapGestureRecognizer(target: self, action: #selector(cancel)) gesture.delegate = self self.dismissView.addGestureRecognizer(gesture) self.collectionView.delegate = self self.collectionView.dataSource = self strongSelf = self }
里面對于UICollectionView的操作可以忽略,dismissView是取消PopupView的按鈕,當(dāng)然并沒有用UIButton,用的是UIView,所以要手動添加點(diǎn)擊事件。
取消
取消PopupWindow比較簡單,將view從其容器中移除,并將其強(qiáng)引用置空。為了實(shí)現(xiàn)從底部消失的效果,仍然用UIView動畫變換y坐標(biāo)實(shí)現(xiàn)。
func cancel() { UIView.animate(withDuration: 0.3) { animation in self.view.frame = CGRect(x: 0, y: UIScreen.main.bounds.height, width: UIScreen.main.bounds.width, height: UIScreen.main.bounds.height) } DispatchQueue.main.asyncAfter(deadline: .now()+0.3) { self.view.removeFromSuperview() } strongSelf = nil }
調(diào)用
在調(diào)用類中實(shí)現(xiàn)PopupWindowDelegate協(xié)議,重寫交互函數(shù)。創(chuàng)建PopupWindow對象,并設(shè)置委托屬性和其他屬性。
let popupWindow = UIStoryboard(name: "DefiniteUI", bundle: nil).instantiateViewController(withIdentifier: "popup") as! PopupWindow popupWindow.delegate = self popupWindow.create().setItems(value: items)
效果
彈出PopWindow:
取消PopWindow:
后記
舉一反三,除了PopupWindow,類似的各種自定義的Dialog都可以這樣去實(shí)現(xiàn),讀者可以去試試。以上就是本文的全部內(nèi)容,希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
iOS開發(fā)--仿新聞首頁效果WMPageController的使用詳解
這篇文章主要介紹了iOS開發(fā)--仿新聞首頁效果WMPageController的使用詳解,詳解的介紹了iOS開發(fā)中第三方庫WMPageController控件的使用方法,有需要的可以了解下。2016-11-11Objective-C實(shí)現(xiàn)冒泡排序算法的簡單示例
冒泡排序即是依次比較相鄰的兩個數(shù),如果后面的數(shù)較小則交換到前面一個數(shù)的位置上,這里我們來看一下Objective-C實(shí)現(xiàn)冒泡排序算法的簡單示例2016-06-06iOS設(shè)計(jì)模式——Category簡單介紹
這篇文章主要介紹了iOS設(shè)計(jì)模式——Category簡單介紹,有興趣學(xué)習(xí)的同學(xué)可以了解一下。2016-11-11iOS應(yīng)用中UICollectionViewCell定制Button
這篇文章主要介紹了iOS應(yīng)用中UICollectionViewCell如何定制Button,設(shè)置每行顯示的按鈕的個數(shù),自定制按鈕的顯示樣式,感興趣的小伙伴們可以參考一下2016-08-08