在市面上幾乎所有的APP中,基本上都有個引導頁面,有的還具有一些登入的功能,更有甚者還需要選擇每個區域的伺服器的節點的操作,我們公司現在做的應用就有這種需求。
大致的實現思路
引導介面,一般都會放幾張輪播圖,我們將在輪播到最後一張圖片的時候顯示出跳轉到登入介面的按鈕,然後在登入介面中會建立選擇伺服器的按鈕,然後再次跳轉到選擇伺服器節點的介面。大致的流程就是這樣。
在輪播到最後一張圖片的時候,建立跳轉按鈕,在這個跳轉的操作中,一般會更改掉本個視窗的根控制器,將根控制器從引導介面轉為登入介面。當進行選擇伺服器節點的操作的時候,這裡就不需要進行根控制器的切換了。
具體的實現過程
此demo的資料,我是放在本地操作的,資料載入是通過plist檔案進行的。
1、搭建引導頁面
在引導頁面中,我們會建立一個輪播器,來展示輪播的圖片。在輪播介面中,往往需要隱藏導航欄的。
/// 導航欄設定
func wjNavgationSettings() {
self.navigationController?.navigationBar.isHidden = true
}
複製程式碼
在頁面即將要載入完成的時候就要隱藏掉導航欄。
導航欄隱藏掉後,為了頁面的美觀,一般也會隱藏掉狀態列,所以在當前控制器還需要新增隱藏狀態列的程式碼。
// 隱藏狀態列
override var prefersStatusBarHidden: Bool{
return true
}
複製程式碼
載入輪播圖片:
self.images = NSMutableArray(capacity: 0)
let imageArray = NSArray(contentsOfFile: Bundle.main.path(forResource: "data", ofType: "plist")!)
if let imgArr = imageArray {
self.images.addObjects(from: imgArr as Array)
}
複製程式碼
這樣就把plist的資料獲取得到了。
搭建輪播器
let imageCount : Double = Double(self.images.count)
let screenW = self.view.frame.size.width
let screenH = self.view.frame.size.height
// 建立輪播圖
let scrollView = UIScrollView()
scrollView.frame = self.view.frame
scrollView.isPagingEnabled = true
scrollView.bounces = false
scrollView.contentSize = CGSize(width: Double(screenW) * imageCount, height: 0) // 頁面的展示的大小
scrollView.showsHorizontalScrollIndicator = false
scrollView.showsVerticalScrollIndicator = false
scrollView.backgroundColor = UIColor.brown
scrollView.delegate = self
self.view.addSubview(scrollView)
self.scrollView = scrollView
複製程式碼
建立圖片展示的imageView以及pageControl。
根據圖片的張數來迴圈建立imageView和建立pageControl
// 建立圖片
for i in 0..<Int(imageCount) {
let imageView = UIImageView()
imageView.image = UIImage(named: "gagi(i + 1)")
imageView.frame = CGRect(x: Double(screenW) * Double(i), y: 0, width: Double(screenW), height: Double(screenH))
imageView.isUserInteractionEnabled = true // 為的是響應鏈能夠被傳遞,不然後新增在其上面的按鈕的點選事件就不能被執行了
scrollView.addSubview(imageView)
self.imageView = imageView
}
// pageControl
let pageControl = UIPageControl()
pageControl.frame = CGRect(x: 137.5, y: 600.0, width: 100.0, height: 20.0) // 暫且寫死資料
pageControl.isUserInteractionEnabled = false
pageControl.hidesForSinglePage = true
pageControl.currentPage = 0
pageControl.numberOfPages = Int(imageCount)
self.view .addSubview(pageControl)
self.pageControl = pageControl
複製程式碼
以上程式碼應該就可以展示輪播的圖片,但是會有個問題就是在輪播圖片的時候,下面的pageControl不會有變化,所以就要scrollview遵守協議,在scrollview在停止滾動的時候就要去修改pageControl。
遵守UIScrollViewDelegate,我們單獨寫在一個extension中,然後需要遵守此協議。
在scrollview進行滑動的時候,頁面展示到最後一張圖片的時候,應該建立出跳轉按鈕。
extension wjGuideVC : UIScrollViewDelegate {
func scrollViewDidScroll(_ scrollView: UIScrollView) {
// 頁面在移動過螢幕一般的時候就修改currentPage
let page = Int(self.scrollView.contentOffset.x / scrollView.frame.size.width + 0.5)
self.pageControl.currentPage = page
// 如果在最後一張照片出現的時候就要建立一個按鈕,進行點選,然後跳轉
if page == self.images.count - 1 {
let btn = UIButton(type: .custom)
btn.frame = CGRect(x: 112.5, y: 500, width: 150.0, height: 50.0)
btn.layer.masksToBounds = true
btn.layer.cornerRadius = 10
btn.backgroundColor = UIColor.cyan
btn.setTitle("點 擊 進 行 跳 轉", for: .normal)
btn.setTitleColor(UIColor.black, for: .normal)
btn.addTarget(self, action: #selector(wjGuideVC.wjModifyRootVCAction(_ :)), for: .touchUpInside)
self.imageView.addSubview(btn)
}
}
}
複製程式碼
以上的程式碼需要注意的地方就是,這個按鈕是放在imageView上,也就是說如果沒有寫上imageView.isUserInteractionEnabled = true
這句話,會導致這個事件的傳遞會被中斷,在點選到按鈕的時候,事件沒法進行響應,也就沒法事件傳遞到UIApplication,事件的分發也不會讓按鈕去處理,因為分發到imageView的時候就中斷了,整個事件也會拋棄掉。所以加上這就話,就能把事件傳遞下去,然後事件的分發也會找到相應的按鈕去執行。
以上的程式碼就基本完成了整個引導頁面的UI搭建,但在按鈕的點選事件中,該如何處理這個修改根控制器。
接下來就要知曉是誰要修改掉根控制器。
在app中,整個視窗就是個window,需要修改根控制器的也是這個window(其實window在程式中還有別的)。
在點選按鈕後的操作:
- 讓導航欄顯示出來
- 記錄引導頁面已經顯示過的資料存到本地去(還可以將本個app的版本號存到本地去)
- 修改根控制器
- 頁面的跳轉
// 修改根控制器的按鈕點選事件
func wjModifyRootVCAction(_ btn : UIButton) {
let vc = ViewController()
vc.navigationController?.navigationBar.isHidden = false
// 把已經出現過的引導頁的結果記錄到本地
UserDefaults.standard.set(true, forKey: "isShowGuidePage")
// 這個地方完全可以將app的version也存到本地去,然後在每次進入到app的時候就判斷版本號
let app = AppDelegate()
let nav = UINavigationController(rootViewController: vc)
app.window?.rootViewController = nav
self.present(nav, animated: true, completion: nil)
}
複製程式碼
由於這個頁面的跳轉是通過present的形式跳轉的,所以在跳轉的時候會自動隱藏掉導航欄,即便是在下個控制器中讓導航欄顯示出來,也會隱藏掉的,所以要在下個介面顯示出導航欄,就要推出整個導航控制器,這樣就不會隱藏掉導航欄。
以上就是引導頁面的全部邏輯。
在程式載入的時候,第一次載入會去載入檢視本地有沒存入引導頁顯示的記錄,如果有的話就就直接使登入介面作為根控制器,如果沒有就使得引導頁面作為根控制器。
func wjRootVCSettings() { // 可以將版本號也作為參考的依據。
let isShowGuide = UserDefaults.standard.bool(forKey: "isShowGuidePage")
if isShowGuide == true {
let vc = ViewController()
let nav = UINavigationController(rootViewController: vc)
self.window?.rootViewController = nav
} else {
let guideVC = wjGuideVC()
let nav = UINavigationController(rootViewController: guideVC)
self.window?.rootViewController = nav
}
}
複製程式碼
2、登入介面
其實登入頁面沒啥好說,就demo而言就新增了一個跳轉按鈕還有兩個textField。為了方便除錯,不至於每次刪掉app再進行除錯,做了個重置的操作的按鈕,是放在導航欄上的。
// 導航欄設定
func wjNavigationSettings() {
self.title = "登入介面"
// 恢復isShowGuidePage為false
let btn = UIButton(type: .custom)
btn.setTitle("重置", for: .normal)
btn.setTitleColor(UIColor.black, for: .normal)
btn.bounds = CGRect(x: 0, y: 0, width: 50, height: 30)
btn.addTarget(self, action: #selector(ViewController.wjModifyDataAction(_ :)), for: .touchUpInside)
self.navigationItem.leftBarButtonItem = UIBarButtonItem(customView: btn)
}
// 修改存在本地的資料
func wjModifyDataAction(_ btn : UIButton) {
UserDefaults.standard.set(false, forKey: "isShowGuidePage")
self.dismiss(animated: true, completion: nil) // 只有當從引導頁跳轉過來才有效,如果是再次執行demo跳轉進來的是無效的。堆疊中是沒有建立引導頁的。
}
複製程式碼
3、伺服器選擇介面
也就是建立了兩個textField,當兩個輸入框都有值的時候,按鈕才能進行點選,載入資料。如果沒有輸入內容會有相應的提示。
當輸入框均有內容的時候,才載入tableView,然後在去載入資料,同樣資料是通過plist檔案進行載入的。
// 資料載入
// 懶載入
lazy var wjServerListArr : NSMutableArray = {
let dataArray = NSArray(contentsOfFile: Bundle.main.path(forResource: "serverListData", ofType: "plist")!)
let wjServerListArr = NSMutableArray(capacity: 0)
for dict in dataArray! {
let dataDict = dict as! [String : String]
let model = wjServerModel().wjServerModelWithDict(dataDict)
wjServerListArr.add(model)
}
return wjServerListArr
}()
複製程式碼
介面的建立
func wjCreatTableView() {
let screenW = self.view.frame.size.width
let screenH = self.view.frame.size.height
let rect = CGRect(x: 0, y: 250, width: Double(screenW), height: Double(screenH - 250))
UIView.animate(withDuration: 0.5) {
self.tableView = UITableView(frame: rect, style: UITableViewStyle.plain)
self.tableView.backgroundColor = UIColor.white
self.tableView.delegate = self
self.tableView.dataSource = self
self.view.addSubview(self.tableView)
}
}
複製程式碼
協議的遵守,同樣是放在extension中進行處理。便於管理。
// MARK:- UITableViewDataSource
extension wjServerSelectVC : UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.wjServerListArr.count;
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let iden = "serverCell"
var cell = tableView.dequeueReusableCell(withIdentifier: iden)
if cell == nil {
cell = UITableViewCell(style: .subtitle, reuseIdentifier: iden)
}
let model = self.wjServerListArr[indexPath.row] as! wjServerModel
cell?.textLabel?.text = model.serverName
cell?.detailTextLabel?.text = model.serverIP
return cell!
}
}
// MARK:- UITableViewDelegate
extension wjServerSelectVC : UITableViewDelegate {
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
// 建立彈出框
// let model = self.wjServerListArr[indexPath.row] as! wjServerModel
// print("index is : (model.serverName) : (model.serverIP)")
self.wjShowMessage(indexPath, "選擇此伺服器?") { (model) in
print("index is : (model.serverName) : (model.serverIP)")
self.dismiss(animated: true, completion: nil)
}
}
}
複製程式碼
總結
其實整個demo的難度不大,也就是一些點需要注意。
- 在建立scrollview的時候,整個頁面的展示的大小和pageControl是根據圖片的張數確定的。
- 在建立引導頁面的時候,隱藏掉導航欄和狀態列的方法。
- 在跳轉的時候,需要把導航欄顯示出來的方法,就是退出導航控制器,而不是登入介面的控制器。
- 需要在app進行載入的時候就要判斷存放在本地的資料,根據這個資料來設定根控制器。
程式碼