ichigoryume programming blog

プログラミングに関する備忘録。主にHTML5, C#, Swiftなど。

Rubyスクリプトを実行してテーブルにデータを挿入する

Rubyスクリプトでテーブルにデータを挿入する場合

begin
  user = User.new
  user.name = "hoge"
  user.save
end

のように書いたスクリプトをlib/tasks以下などに置き、

rails runner lib/tasks/hoge.rb

のように実行する。

タグにデータ属性を追加し、値をRubyで設定する

データ属性を追加する場合は

%p{data-hoge:"aaaa"}

とするとエラーになるので

%p{data:{ hoge:"aaaa" }}

のようにする。

またRubyコードの実行結果を値に入れたい場合は、例えばfooオブジェクトのnameプロパティをセットするならば

%p{data: {hoge:foo.name}

のようにする

デバイスの方向変化を検出して表示を切り替えるViewControllerのサンプルコード

ポイント

  • 方向の変化はNotificationCenter.default.addObserver()でハンドラをセットすることで検出できる
  • 現在の方向はUIDevice.current.orientationで取得できるが、単純に縦か横かを判定するだけならViewの縦横どちらが大きいかで判定すれば十分
  • NotificationCenter.default.addObserver()のハンドラは、起動時になぜか数回コールされてしまうので、前回の方向を記憶し、本当に方向がかわったときのみ表示の変更を行うコードを入れるのがいい

コード

import UIKit

class ViewController: UIViewController {

    @IBOutlet weak var label: UILabel!
    
    var lastIsVertical:Bool = false
    
    override func viewDidLoad() {
        super.viewDidLoad()

        NotificationCenter.default.addObserver(self, selector: #selector(deviceOrientationChanged), name: NSNotification.Name.UIDeviceOrientationDidChange, object: nil)
    }

    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
        
        print("view will appear")
        if view.frame.width < view.frame.height {
            setupVertical()
        }
        else {
            setupHorizontal()
        }
    }

    @objc private func deviceOrientationChanged(notification:NSNotification) {
        
        print("orientation changed")
        
        if view.frame.width < view.frame.height {
            if lastIsVertical == false {
                setupVertical()
            }
        }
        else {
            if lastIsVertical == true {
                setupHorizontal()
            }
        }
        
    }
    
    func setupVertical() {
        print("vertical")
        label.text = "vertical"
        
        lastIsVertical = true
    }
    
    func setupHorizontal() {
        print("horizontal")
        label.text = "horizontal"

        lastIsVertical = false
    }
}

実行結果

f:id:ichigoryume:20180908105526p:plainf:id:ichigoryume:20180908105531p:plain

カスタムViewのxibファイルをStoryboard(InterfaceBuilder)で再利用する

概要

  • よく使う、あるいは複数配置するようなGUI部品は、別途xibファイルとViewサブクラスを用意して部品化しておけば簡単に再利用できる。
  • Storyboardに配置してInterfaceBuilderでプレビューすることも可能

手順

  • まずxibファイルを追加する。プロジェクトツリーの右クリックメニューからNew Fileを選択し、User Interface の欄にあるViewを選択し、名前をつけて保存。

f:id:ichigoryume:20180907162357p:plain

  • 追加したxibファイルをInterfaceBuilderで編集(略)
  • Swift Fileを追加し、修飾子@IBDesignableを付与し、UIViewを継承するクラスを作る。
@IBDesignable class MySubView : UIView {
    
    override init(frame: CGRect) {
        super.init(frame: frame)
        loadFromNib()
    }
    
    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        loadFromNib()
    }

    override func prepareForInterfaceBuilder() {
        loadFromNib()
    }
    
    private func loadFromNib() {
        let v = UINib(nibName: "MySubView", bundle: Bundle(for: MySubView.self)).instantiate(withOwner: self, options: nil)[0] as! UIView
        v.frame = self.bounds
        addSubview(v)
    }
}
  • ポイント
    • @IBDesignableを忘れずに!
    • init()を2種と、prepareForIntafeceBuilder()メソッドを記述すること
    • loadFromNib()メソッド内のUINibメソッドでは、nibNameに先に作成したxibファイルの拡張子無しの名前を与え、Bundle()のfor:にはビュークラス名.selfを与える。
  • 一旦ビルド
  • 次に、この部品を使う側のStoryboardを開き、Viewを画面に配置して、インスペクターでCustome Classで作成したViewを指定する。

f:id:ichigoryume:20180907163256p:plain

以上で部品作り完成。

作成した部品ViewクラスのプロパティをInterfaceBuilderでセットできるようにする

  • プロパティに@IBInspectable修飾子を付与すると可能になる
    @IBInspectable var myColor:UIColor = UIColor.black {
        didSet {
            self.layer.backgroundColor = myColor.cgColor
        }
    }

f:id:ichigoryume:20180907164340p:plain

クラスインスタンス配列の永続化

概要

  • 自作クラスの配列の保存と読み込みを例にしたサンプルコード

サンプルコード

以下のようなクラスがあったとして

class Person  {
    
    var name:String = ""
    var age:Int = 0

    init(age:Int, name:String) {
        self.age = age
        self.name = name
    }
}

このクラスを永続化するには、NSObjectとNSCodingを継承させ、encode()メソッドと、decodeに相当するinit()メソッドを記述する

class Person : NSObject, NSCoding {
    
    var name:String = ""
    var age:Int = 0

    init(age:Int, name:String) {
        self.age = age
        self.name = name
    }
    
    func encode(with aCoder: NSCoder) {
        aCoder.encode(self.name, forKey: "name")
        aCoder.encode(String(self.age), forKey: "age")
    }
    
    required init?(coder aDecoder: NSCoder) {
        self.name = aDecoder.decodeObject(forKey: "name") as? String ?? ""
        self.age = Int(aDecoder.decodeObject(forKey: "age") as? String ?? "0") ?? 0
    }
}

ageのようなString以外の型は一旦String型に変換するのがポイント。

このクラスを使った

    var array:Array<Person>

のような配列があるとして、これを保存するには以下のようなコードを書く。

    func save() {
        
        let ret = NSKeyedArchiver.archiveRootObject(array, toFile: getPath())
        if(ret == false) {
            print("save error.")
        }
        else {
            print("save succeeded")
        }
        
    }

    func getPath() -> String {
        let directory = (NSHomeDirectory() as NSString).appendingPathComponent("Documents")
        return (directory as NSString).appendingPathComponent("Persons.dat")
    }

ロードは以下のようなコードを書く。

    let persons = NSKeyedUnarchiver.unarchiveObject(withFile: getPath()) as? Array<Person>

Media & Apple Musicへのアクセス許可を繰り返し求める方法

概要

  • プログラムからiTnuesライブラリにアクセスしたり、MPMusicPlayerControllerを使って曲を再生したりする場合、ユーザーに「メディアとApple Music」へのアクセスを許可してもらう必要がある。
  • MPMediaLibrary.requestAuthorization()メソッドをコールすると許可を求めるダイアログが表示される。

f:id:ichigoryume:20180906092029p:plain

  • このダイアログは一度しか表示されない。同メソッドを再度コールしても、アプリを一旦再起動してコールしても何も表示されない。
  • この挙動は初回コール時に「OK」をタップしてもらえた場合はいいが、「Don't Allow」をタップされた場合厄介。
  • 一旦「Don't Allow」された場合は設定画面でアクセスを許可してもらえばいい。

f:id:ichigoryume:20180906092518p:plain

  • なので「Don't Allow」された場合は、2回目以降、設定画面へ誘導するダイアログを自前で表示すると親切。
  • ユーザがアクセスを許可しているかどうかは、MPMediaLibrary.authorizationStatus()メソッドで判定できる。

手順

まず、MPMediaLibrary.requestAuthorization()メソッド初回実行時のダイアログに表示される、アクセス理由に相当する文字列をInfo.plistに設定する。Info.plistにこの設定項目がないとダイアログが表示されることなくアプリがクラッシュする。

  • key: Privacy - Media Library Usage Description
  • Value: To Play Music(例)

f:id:ichigoryume:20180906093938p:plain

続いて、2回目以降、設定画面へ誘導するダイアログを表示するメソッドを作成する

    func displayPermissionAlertFromViewController() {
        
        let alert = UIAlertController(
            title: "This application needs to access your music library. Please allow it to access \"Meidia & Apple Music\" at Settings.",
            preferredStyle: .alert
        )
        
        alert.popoverPresentationController?.sourceView = self.view

        let cancelAction = UIAlertAction(title: "Cancel", style: .default, handler: nil)
        alert.addAction(cancelAction)
        
        let okAction = UIAlertAction(title: "GoToSettings", style: .default) { _ in
            if let url = URL(string: UIApplicationOpenSettingsURLString) {
                // 設定画面をオープン
                UIApplication.shared.open(url, options: [:], completionHandler: nil)
            }
        }
        alert.addAction(okAction)
        
        self.presentedViewController?.dismiss(animated: false, completion: nil)
        present(alert, animated: true, completion: nil)
    }

最後に、許可状況を確認したうえでクロージャーを実行するメソッドを作るとこんな感じ。

    fileprivate func checkMediaPlayerPermission(completion:@escaping () -> Void) {
        
        let status = MPMediaLibrary.authorizationStatus()
        
        switch status {

        case .authorized:
            completion()
            break

        // 初回起動時はnotDetermined
        case .notDetermined:
            MPMediaLibrary.requestAuthorization { status in
                if status == .authorized {
                    completion()
                }
            }
            break
            
         // 一度拒否されている場合
        case .denied, .restricted:
            displayPermissionAlertFromViewController()
        }
    }