iT邦幫忙

2019 iT 邦幫忙鐵人賽

DAY 9
0

自己動手做一個到處都可以重複利用的插件吧!

https://ithelp.ithome.com.tw/upload/images/20181024/20107701ROlPHkHcKZ.jpg

前言:

雖然目前有許多第三方的程式庫可以讓我們免於處理許多麻煩的事情,但是今天你只需要一個簡單的畫面或是功能,卻引入了一個龐大的程式庫,那是不是倒不如自己來寫一個簡單的程式庫|插件來使用它。這次教學我們會自己做一個簡單且帶有動畫的 LoadingView,讓我們在各個畫面重複利用。


製作 LoadingView API

大家應該還記得我們上次在 WebView 上製作的 LoadingView 吧!

https://ithelp.ithome.com.tw/upload/images/20181024/20107701GrIU0R16z4.png

loadingView

因為我們可能許多畫面都有加載資料的需求,所以假如我們使用 Storyboard 來做的話我們沒辦法有效的重複利用它。因此在這邊我們會先把這個 loadingView 給刪除,接著我們來寫一個程式庫來使用它。首先我們會定義一個叫做 AnimationLoadingView 的 Class:

public class AnimationLoadingView {
    
    // 宣告元件
    private static var loadingView: UIView?
    private static var spinner: UIActivityIndicatorView?
    private static var messageLabel: UILabel?
    
    // 開始加載
    public static func startLoading(message: String?) {
        // 如果 loading 不存在,我們會實例化它以及其他元件
        // 這邊我們不希望還帶一個要加在哪個 view 的參數,所以這我們將 loadingView 加到 UIWindow 上
        if loadingView == nil, let window = UIApplication.shared.keyWindow {
            // 元件設置,依照個人設置即可
            loadingView = UIView()
            loadingView?.frame = CGRect(x: 0, y: 0, width: 100, height: 100)
            loadingView?.center = window.center
            loadingView?.backgroundColor = .darkGray
            loadingView?.alpha = 0.9
            loadingView?.layer.cornerRadius = 10
            loadingView?.clipsToBounds = true
            spinner = UIActivityIndicatorView()
            spinner?.frame = CGRect(x: 0, y: 0, width: 50, height: 50)
            spinner?.center = CGPoint(x: loadingView!.bounds.midX, y: loadingView!.bounds.midY - 15)
            spinner?.style = .whiteLarge
            spinner?.startAnimating()
            messageLabel = UILabel()
            messageLabel?.frame = CGRect(x: 0, y: loadingView!.bounds.midY + 20, width: 200, height: 20)
            messageLabel?.center = CGPoint(x: window.center.x, y: window.center.y + 20)
            messageLabel?.textColor = .white
            messageLabel?.font = UIFont(name: "PingFangTC-Medium", size: 13)
            messageLabel?.textAlignment = .center
            messageLabel?.text = message
            loadingView?.addSubview(spinner!)
            window.addSubview(loadingView!)
            window.addSubview(messageLabel!)
        }
    }
    
    // 停止加載
    public static func endLoading(message: String?) {
        if let loadingView = loadingView, let window = UIApplication.shared.keyWindow {
            // 停止加仔載動畫
            UIView.animate(withDuration: 0.4, animations: {
                loadingView.frame = CGRect(x: 0, y: 0, width: 200, height: 50)
                loadingView.center = window.center
                spinner?.center = CGPoint(x: loadingView.bounds.midX, y: loadingView.bounds.midY)
                messageLabel?.center = window.center
                messageLabel?.text = message
                spinner?.alpha = 0
            }) { _ in
                UIView.animate(withDuration: 1.2, animations: {
                    messageLabel?.center.y -= 50
                    messageLabel?.alpha = 0
                    loadingView.center.y -= 50
                    loadingView.alpha = 0
                }, completion: { _ in
                    // 刪除元件
                    remove()
                })
            }
        }
    }
    
    // 刪除方法
    public static func remove() {
        self.loadingView?.removeFromSuperview()
        self.messageLabel?.removeFromSuperview()
        self.spinner?.removeFromSuperview()
        self.loadingView = nil
        self.messageLabel = nil
        self.spinner = nil
    }
}

這邊我們也建立了一個 static function 這樣我們就不需要實例化一個 Object 才能使用方法,此外你可以看到我們前面會加上一些 pulic 或是 private,這些東西稱作為 Access Control,你可以想像這些是訪問這個 class 的權限,你不會希望你的 class 的每個東西都能被任意修改,所以我們會加上這些前綴為他設立權限,在之後我們將我們自訂的 API 打包成 framework 時,我們必須明確設定好這些訪問的權限。

裡面對每個元件的設置都可以照著個人喜好來設定,不一定要跟我相同。
詳情可以參考 —【 官方文檔 Access Control


# 設置我們的 AnimationLoadingView 插件

這邊我們之後就可以透過 AnimationLoadingView 直接呼叫我們所定義的 startLoading(message:)、endLoding(message:) 以及 remove 的方法,這邊我們將它放入我們的 WebView 畫面中,我們設置:

extension BookWebViewController: WKNavigationDelegate {    
    func webView(_ webView: WKWebView, didStartProvisionalNavigation navigation: WKNavigation!) {
        AnimationLoadingView.startLoading(message: "加載中...")
    }
    
    func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
        AnimationLoadingView.endLoading(message: "載入完成!")
    }
    
    func webView(_ webView: WKWebView, didFail navigation: WKNavigation!, withError error: Error) {
        AnimationLoadingView.endLoading(message: "載入失敗!")
    }
}

接著讓我們來看一下畫面:

image

接著我們會碰到一個問題,因為我們的 LoadingView 是加在我們的 UIWindow 上,所以跨頁面的時候依然會留在畫面上。

image

LoadingView 依然停在畫面上

這時我們在我們 viewWillDisappear 就可以呼叫我們 remove() 的方法:

override func viewWillDisappear(_ animated: Bool) {
  super.viewWillDisappear(animated)
  AnimationLoadingView.remove()
}

如此一來在我們離開畫面的時候,我們就會先把 window 上的畫面給移除掉,之後在我們畫面離開的時候都會先把 LoadingView 給移除掉了。


後記:

那麼我們這次自己製作一個程式庫|API 的教學就到這邊結束了,當然我這個 API 可能還有更好的作法可以修正,大家有什麼做法也可以留言給我參考互相交流,希望大家能夠透過這次教學學到如何製作一個可以重複使用的插件,我們下篇文章見。


上一篇
Day 08: 使用搜尋列來找尋書籍吧!
下一篇
Day 10: 做個資料的下拉式更新!
系列文
一天一蘋果,Bug 遠離我。30

尚未有邦友留言

立即登入留言