2017年10月15日日曜日

帰ってきた凛としてSwift(くるくる編)

泣きながらiPhoneX対応をしています。なんであんなにワーニングが出るんでしょうか、Xcode9のStroyboard。

ウェブページを表示する画面を作っていたのですが、また見慣れないSwiftLintのワーニングが出たので、シリーズ続行です。


UIWebViewがdeprecatedになったので、WKWebViewを使うことにしました。

ページのロード中を示すインジケータの表示ですが、UIWebViewならUIWebViewDelegateを実装すれば開始と終了のイベントが取得できるので、丸いアニメーションインジケータを表示するのが簡単だと思います。WKWebViewでも、WKNavigationDelegateを実装すれば同様にコントロールできます。

(※ 今回のソースは全てSwift4で、最小限動くところまで削るためにターゲットをiOS11.0以降にしました。ご注意を。)

import UIKit
import WebKit

class SomeWeb4ViewController: UIViewController, WKNavigationDelegate {

    @IBOutlet weak var webView: WKWebView!
    @IBOutlet weak var indicatorView: UIActivityIndicatorView! //くるくるインジケータ
    
    override func viewDidLoad() {
        super.viewDidLoad()

        indicatorView.hidesWhenStopped = true //くるくるしてないときは、非表示
        
        //webページのロード
        webView.navigationDelegate = self
        let myURL = URL(string: "https://www.apple.com/")
        let myRequest = URLRequest(url: myURL!)
        webView.load(myRequest)
    }
    
    func webView(_ webView: WKWebView, didStartProvisionalNavigation navigation: WKNavigation!) {
        indicatorView.startAnimating() //くるくる開始
    }
    
    func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
        indicatorView.stopAnimating() //くるくる停止
    }
}
まぁ、見たとおりです。WKNavigationDelegateで、Webページのロードの開始と停止のイベントを取得しています。処理もシンプルで分かりやすいですね。

ただ、WKWebViewはKVOに対応しているので、もう少し気の利いた制御をするのがお約束なのでしょうか。そんな情報が多いです。そこで、それらを真似てナビゲーションバーの下に、横に伸びるインジケータ表示にしました。

import UIKit
import WebKit

class SomeWeb2ViewController: UIViewController {

    @IBOutlet weak var webView: WKWebView!
    
    var progressView = UIProgressView()
    
    override func viewDidLoad() {
        super.viewDidLoad()

        //プログレスバー
        let progFrame = CGRect(x: 0.0,
                               y: self.navigationController!.navigationBar.frame.size.height - 2.0,
                               width: self.navigationController!.navigationBar.frame.size.width,
                               height: 10.0)
        progressView = UIProgressView(frame: progFrame)
        progressView.progressViewStyle = .bar
        self.navigationController?.navigationBar.addSubview(progressView)
        
        //ここで、webViewのobserverを設定して
        webView.addObserver(self, forKeyPath: "loading", options: .new, context: nil)
        webView.addObserver(self, forKeyPath: "estimatedProgress", options: .new, context: nil)

        //webページのロード
        let myURL = URL(string: "https://www.apple.com/")
        let myRequest = URLRequest(url: myURL!)
        webView.load(myRequest)
    }
    
    //ここで進捗を受信
    override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
        if keyPath == "estimatedProgress"{
            progressView.setProgress(Float(webView.estimatedProgress), animated: true)
        } else if keyPath == "loading"{
            UIApplication.shared.isNetworkActivityIndicatorVisible = webView.isLoading
            if webView.isLoading {
                progressView.setProgress(0.1, animated: true) //プログレスの開始
            } else {
                progressView.setProgress(0.0, animated: false) //プログレスを消去
            }
        }
    }
    
    deinit {
        webView.removeObserver(self, forKeyPath: "estimatedProgress")
        webView.removeObserver(self, forKeyPath: "loading")
    }
}
webViewに、"loading"と"estimatedProgress"のobserverを付加して、ロードの開始、進捗、終了を取得しています。

ちなみにdeinitにある、observerの削除がないと落ちるとのことですが、11.0では落ちなかったです。だから不要、というわけでもなさそうですが。

もちろんこれでちゃんと動作するのですが、SwiftLintがワーニングを出力します。


・block_based_kvo
Block Based KVO Violation: Prefer the new block based KVO API with keypaths when using Swift 3.2 or later. (block_based_kvo)


new block based KVO API with keypaths ってなに?って感じですが、 どうやらWKWebViewに付加したobserverを func observeValue(){} で受信する方法は古いようです。

そこで、Adopting Cocoa Design Patternsの「Key-Value Observing」を参考にして、 NSKeyValueObservationを使った方法に書き換えてみました。
import UIKit
import WebKit

class SomeWeb3ViewController: UIViewController {
    
    @IBOutlet weak var webView: WKWebView!
    var progressView = UIProgressView()
    
    var obsLoading: NSKeyValueObservation? //←これ
    var obsEstimatedProgress: NSKeyValueObservation? //←これ
    
    override func viewDidLoad() {
        super.viewDidLoad()

        //プログレスバー
        let progFrame = CGRect(x: 0.0,
                               y: self.navigationController!.navigationBar.frame.size.height - 2.0,
                               width: UIScreen.main.bounds.size.width,
                               height: 10.0)
        progressView = UIProgressView(frame: progFrame)
        progressView.progressViewStyle = .bar
        self.navigationController?.navigationBar.addSubview(progressView)
        
        //ここから→
        obsLoading = observe(\.webView.loading) { _, _ in
            UIApplication.shared.isNetworkActivityIndicatorVisible = self.webView.isLoading
            if self.webView.isLoading {
                self.progressView.setProgress(0.1, animated: true) //プログレスの開始
            } else {
                self.progressView.setProgress(0.0, animated: false) //プログレスを消去
            }
        }
        obsEstimatedProgress = observe(\.webView.estimatedProgress) { _, _ in
            self.progressView.setProgress(Float(self.webView.estimatedProgress), animated: true)
        }
        //←ここまで

        let myURL = URL(string: "https://www.apple.com/")
        let myRequest = URLRequest(url: myURL!)
        webView.load(myRequest)
    }
}
Swift4におけるKVOのベストプラクティスがよくわかっていないので、本当にこれが正しいのか自信がないのですが、とりあえずワーニングが消えて、ちゃんと動きました。内心、ちょっと怪しいとは感じています。ま、そのときはそのときで随時修正します。

それにしても、なにこのバックスラッシュ。Swiftこわい。


♪♪♪


こちらもどうぞ。
凛としてSwift
凛としてSwift (血闘編ノ壱)
凛としてSwift (血闘編ノ弐)
凛としてSwift (血闘編ノ参)


2017年10月1日日曜日

チューニングなどという(そしてリベンジ編)

前回の「チューニングなどという(これはアカン編)」で、ハーモニクスを使ったチューニング方法はダメだという話を書きました。今回は、じゃあそれは何の役にも立たないかというかというと、「ちょっと待て、そこは考えどころじゃないか?」という話です。

また、5弦を基準に4弦を合わせる場合に限定して書きます。

正しいチューニング方法では、5弦の5フレットの実音に4弦の開放を合わせるというのは「チューニングなどという(まずは基本ですよ編)」で書きました。

そして、ハーモニクスを使ったチューニング方法では、5弦の5フレットのハーモニクスに4弦の7フレットのハーモニクスを合わせるのでした(でも実際は4弦が少しだけ低くなってしまう)。



そして、2つの音を同じにするよう合わせるには、音を同時に出して唸りがなくなるようにすれば良いことも書きました。

ここからは実際にチューニングをする時の動作についてです。

実音を使った正しいチューニングでは、5弦の5フレットを左手で押さえていないといけないので、ふたつの音を聞きながらペグを回すことはできません。右手でペグを回すという手もありますが、それでは両手が交差して大変ですし、5フレットを押さえている左手に変に力が入って音程も安定しないでしょう。そんなわけで、音をチェックしてはペグを回し、音をチェックして回し、の繰り返しになります。

うむ、これはそういうものなのでしょうがないですな。うんうん。

それに対して、ハーモニクスを使った方法は、音を出している間左手が自由になりますから、音を聞きながらペグを回すことができます。これはちょっと便利ですよね。

そこで、だいたい近いところまではハーモニクスを使ってから、仕上げに実音を使ったチューニングで確認するというのはどうでしょう。

ハーモニクスを使った方法は、基準となる5弦から高い方の4弦を合わせるとき、ピッタリ合わせても、4弦が正しい音程よりも少し低くなるのでした。そこで、そこからは実音でチェックしながら、少しずつ4弦を上げていって合わせるといった方法です。

ペグを回す時は低い方から巻き上げていって、正しい音に合わせるというのがお約束でしたが、これにも馴染みます。

基準となる弦から低い方の現に合わせるとき、つまり5弦の7フレットのハーモニクスに、6弦の5フレットのハーモニクスを合わせる時ですが、この場合はぴったり合わせてしまうと、6弦が少し高くなってしまうので、少し手前でやめて、そこからは、やっぱり実音で確認しながら少しづつ上げていくのがいいと思います。

ハーモニクスでざっくり合わせて、実音で正確にチェック。これですよ。

普段ギターの弦は、張りっぱなしにしている人が多いと思いますが、「ネックが弱いので、弾かないときは弦を緩めておかないとちょっと不安なんだよね」というような方は、この"ハーモニクスでざっくり、実音で正確に"というやり方は便利なのではないでしょうか。そうでなくても、弦の交換時など、全くチューニングがあっていない状態からチューニングする時には役に立ちそうです。

今回は、役に立たないチューニング方法を、なんとか使えないものかという視点で考えてみました。どうでしょうか。





「チューニングなどという(だからチューナー編)」に続きます。