しめ鯖日記

swift, iPhoneアプリ開発, ruby on rails等のTipsや入門記事書いてます

【Swift】ファイル書き込みで「Error Domain=NSCocoaErrorDomain Code=518 "指定されたURLタイプがサポートされていないため、ファイルを保存できませんでした。」エラーが出た時の対応

表題のエラーが出た時の対処法です。
今回は下のように画像をファイル保存をしようとしたら発生しました。

let path = "/xxx/yyy/zzz"
let image = UIImage()
let data = UIImageJPEGRepresentation(image, 80)
if let url = URL(string: path) {
    try? data?.write(to: URL(string: path)!)
}

原因はURL(string: path)で生成したURLにスキーム(file://)が入ってない事でした。
下のようにURL(string: path)の代わりにURL(fileURLWithPath: path)を使うようにしたら保存する事ができました。

let path = "/xxx/yyy/zzz"
let image = UIImage()
let data = UIImageJPEGRepresentation(image, 80)
try? data?.write(to: URL(fileURLWithPath: path))

UIViewのサブクラスで`init?(coder aDecoder: NSCoder)`が必要と言われる理由を調べてみる

UIViewでinitializerを実装すると、init?(coder aDecoder: NSCoder)も実装するように言われます。
今回はinit?(coder aDecoder: NSCoder)と言われる理由などを調べてみます。

f:id:llcc:20170713231749p:plain

init?(coder aDecoder: NSCoder)とはなにか

init?(coder aDecoder: NSCoder)NSCodingプロトコルで定義されているメソッドです。
UIViewはNSCodingに準拠しているので、このメソッドも必須になっています。

public protocol NSCoding {
    public func encode(with aCoder: NSCoder)
    public init?(coder aDecoder: NSCoder)
}

NSCodingプロトコルとは

NSCodingプロトコルとは、そのクラスをアーカイブできるようにするものです。
NSCodingに準拠すれば、以下のようにNSKeyedArchiverを使ってData型への変換をする事ができます。

class MyClass: NSObject, NSCoding {
    override init() {
        super.init()
    }
    
    required init?(coder aDecoder: NSCoder) {
        
    }
    
    func encode(with aCoder: NSCoder) {
        
    }
}

let data = NSKeyedArchiver.archivedData(withRootObject: MyClass())

UIViewもNSCodingに準拠しているのでアーカイブする事ができます。

let data = NSKeyedArchiver.archivedData(withRootObject: UIView())

NSCodingのメソッドはアーカイブ時、復元時に呼ばれます。

public protocol NSCoding {
    public func encode(with aCoder: NSCoder) // アーカイブする時に呼ばれる
    public init?(coder aDecoder: NSCoder) // アーカイブされたものを復元する時に呼ばれる
}

なぜinitにrequireが付くのか

initにrequireが付く件についてテストプロトコルを作って動かしてみました。
試したところ、protocolでinitを定義するとrequireになるようです。

protocol MyProtocol {
    init(test: Int)
}

class MyClass: MyProtocol {
    required init(test: Int) {
    }
}

optionalを付けるとどうなるかも試そうとしたのですが、コンパイルエラーになってしまいました。

@objc protocol MyProtocol {
    optional init(test: Int) // 'optional' cannot be applied to an initializerエラーになる
}

公式ドキュメントにも、requiredになると書いてありました。

https://developer.apple.com/library/content/documentation/Swift/Conceptual/Swift_Programming_Language/Protocols.html

なぜUIViewサブクラスでinitを定義するとinit?(coder aDecoder: NSCoder)が必要になるのか

最後に、initを定義した途端にinit?(coder aDecoder: NSCoder)が必要になる理由についても調べてみました。

こちらはSwiftの仕様でした。
Swiftでは、initializerが1件もない時は親クラスのrequiredを再定義する必要がありません。
そのため、UIViewのサブクラスでinitを定義した途端にinit?(coder aDecoder: NSCoder)が必要になったようです。

class MyClass {
    init() {
    }
    
    required init(test: Int) {
    }
}

class MySubclass: MyClass {
}

NSNotification.Name.UIApplicationDidBecomeActiveの前半部が省略できる理由

表題の件について調べてみました。
NSNotification.Name.UIApplicationDidBecomeActiveはNotificationに渡す変数で、アプリ立ち上げ時に特定のメソッドを呼びたい時などに利用します。

// アプリがアクティブになった時にtestというメソッドを呼び出す
NotificationCenter.default.addObserver(
            self, selector: #selector(self.test), name: NSNotification.Name.UIApplicationDidBecomeActive, object: nil)

今回はNSNotification.Name.UIApplicationDidBecomeActive.UIApplicationDidBecomeActiveと記述できる理由について調べてみました。

Notification(name: NSNotification.Name.UIApplicationDidBecomeActive)
// ↓ のように省略できる
Notification(name: .UIApplicationDidBecomeActive)

結論としては、UIApplicationDidBecomeActiveがクラス変数な為です。
Swiftでは下のようにクラス変数を省略する事ができます。

let test: MyClass = .Test
class MyClass {
    static let Test = MyClass()
}

Stored PropertyComputed Propertyに置き換えても省略できました。

let test: MyClass = .Test
class MyClass {
    static var Test: MyClass {
        return MyClass()
    }
}

Testという変数の型が違うとエラーになります。

// Member 'Test' in 'MyClass' produces result of type 'Int', but context expects 'MyClass' というエラーになる
let test: MyClass = .Test
class MyClass {
    static var Test: Int {
        return 1
    }
}

型が同じでも、他クラスの変数は使えません。

// これもエラーになる
let test: MyClass = .Test
class MyClass {
}

class MyClass2 {
    static var Test: MyClass {
        return MyClass()
    }
}

staticの代わりにclassを使って変数宣言しても問題なく動きます。

let test: MyClass = .Test
class MyClass {
    class var Test: MyClass {
        return MyClass()
    }
}

enumやstructでも同じような挙動になりました。

let test1: MyEnum = .Test
enum MyEnum {
    case test
    
    static var Test: MyEnum {
        return .test
    }
}

let test2: MyStruct = .Test
struct MyStruct {
    static var Test: MyStruct {
        return MyStruct()
    }
}

【Swif】指紋認証時のUIApplicationDelegateメソッドに関する検証

iOSで指紋認証をした時、AppDelegateのどのメソッドが呼ばれるかを調査しました。
指紋認証の方法は下記事を元にしました。

www.cl9.info

検証

アプリを作ったら、AppDelegateを以下のように修正します。
これで、指紋認証時にどのメソッドが呼ばれるかを検証できます。

import UIKit
import LocalAuthentication

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
    var window: UIWindow?
    
    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
        
        print(#function)
        DispatchQueue.main.asyncAfter(wallDeadline: .now() + 5.0, execute: {
            let context = LAContext()
            var error: NSError?
            if context.canEvaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, error: &error) {
                context.evaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, localizedReason: "テスト") { success, error in
                    
                }
            }
        })
        
        return true
    }
    
    func applicationWillResignActive(_ application: UIApplication) {
        print(#function)
    }
    
    func applicationDidEnterBackground(_ application: UIApplication) {
        print(#function)
    }
    
    func applicationWillEnterForeground(_ application: UIApplication) {
        print(#function)
    }
    
    func applicationDidBecomeActive(_ application: UIApplication) {
        print(#function)
    }
}

アプリを立ち上げると以下のログが出力されます。

application(_:didFinishLaunchingWithOptions:)
applicationDidBecomeActive

指紋認証アラートが立ち上がる時は以下のログが出力されました。

applicationWillResignActive

f:id:llcc:20170706231057p:plain

認証 or キャンセルをすると以下のログが出力されます。

applicationDidBecomeActive

ちなみに「アプリをバックグラウンドに移動 → アプリ立ち上げ」の時は下のログが出力されます。

applicationWillResignActive
applicationDidEnterBackground
applicationWillEnterForeground
applicationDidBecomeActive

まとめ

指紋認証の時は、以下のDelegateメソッドが呼ばれる。

applicationWillResignActive
applicationDidBecomeActive

アプリをバックグラウンドに移動&立ち上げすると、以下のDelegateメソッドが呼ばれる。

applicationWillResignActive
applicationDidEnterBackground
applicationWillEnterForeground
applicationDidBecomeActive

【Reporter】iTunesConnectの売上取得をID/Pass方式からAccessToken形式に切替

iTunesConnectの売上情報をAPIで取得する部分の修正をしました。
以前はID/Passwordを使った取得方式だったのですが、Appleから「ID/Password方式は2017年8月から使えなくなるよ」というメールが来たのでAccessTokenで取得する方式に変更しました。

ID/Passwordを使った方法

今までは下のようなコマンドで認証情報を取得していました。

{vendor_id}は、iTunesConnectの「売上とトレンド」の「売上とトレンドのレポート」のアカウント名の隣に表示されています。(2019年02月現在)

java -jar Reporter.jar p=Reporter.properties Sales.getReport [vendor_id], Sales, Summary, Daily, [日付(20170701など)]

認証情報はReporter.propertiesファイルに記載します。
これは上のコマンドを打つパスに配置してください。

userID/passwordはiTunesConnectにログインするID/Passwordです。

userID = [apple_id]
password = [password]

Mode=Robot.xml

SalesUrl=https://reportingitc-reporter.apple.com/reportservice/sales/v1
FinanceUrl=https://reportingitc-reporter.apple.com/reportservice/finance/v1

上コマンドを叩くと、S_D_[vendor_id]_20170630.txt.gzというファイルが作成されます。
解凍すると、以下のようなアプリの日時レポートが取得できます。

Provider Provider Country    SKU Developer   Title   Version Product Type Identifier Units   Developer Proceeds  Begin Date  End Date    Customer Currency   Country Code    Currency of Proceeds    Apple Identifier    Customer Price  Promo Code  Parent Identifier   Subscription    Period  Category    CMB Device  Supported Platforms Proceeds Reason Preserved Pricing   Client
APPLE   US  com.mydomain    DeveloperName   AppName 1   1   0   06/30/2017  06/30/2017  AUD AU  AUD xxxxxxxxxxxx    0                   Business        iPhone  iOS          

AccessTokenを使った方法

次は上のレポートをAccessTokenを使って取得します。
Reporter.propertiesのuserIDとpasswordを消してAccountとAccessTokenを追加します。

Account = [AccountNumber]
AccessToken = [AccessToken]

Mode=Robot.xml

SalesUrl=https://reportingitc-reporter.apple.com/reportservice/sales/v1
FinanceUrl=https://reportingitc-reporter.apple.com/reportservice/finance/v1

AccessTokenはiTunesConnectの「売上とトレンド」の「売上とトレンドのレポート」の右上にある「レポートについて」の右のハテナマークから生成します。
このトークンは半年で期限が切れるため、都度更新する必要がありそうです。

AccountNumberは、AccessTokenを記入後に以下のコマンドを実行することで取得できました。

java -jar Reporter.jar p=Reporter.properties Sales.getAccounts

以上でAccessTokenへの切り替えは完了です。

参考URL

Reporter User Guide 3.0

カイ二乗検定とは何かを調べてみる

統計について調べていたら「カイ二乗検定」という単語が出てきたので調査。

カイ二乗検定とは

カイ二乗検定 - Wikipedia

帰無仮説が正しければ検定統計量が漸近的にカイ二乗分布に従うような統計学的検定法の総称である。次のようなものを含む。

Wikipediaによるとカイ二乗検定とは上のような定義になります。
同じくWikipediaによると、以下のような統計学的検定法の総称のようです。

定義を読むだけでは難しいので、実際の問題を解きながら勉強してみようと思います。

カイ二乗検定の具体例

カイ二乗検定 - 大人になってからの再学習

上のサイトを参考に勉強しました。
こちらのサイトの例2(サイコロを複数回振った結果を元に、サイコロが歪んでいると言えるか求める)の手順を見たところ、カイ二乗検定は下の手順で正しさを検証するようです。

  1. 期待する値(期待度数)、上の例だとサイコロのそれぞれの値が出る確率を算出する
  2. (“実際にある値が出た数” - 期待度数)の2乗 / 期待度数を算出する
  3. それぞれの値について、2で出した値を足し合わせる(この結果をカイ二乗値と呼ぶ)
  4. 今回の問題に関する自由度を求める(今回は項目数6なので6-1で5になる)
  5. 今回の問題に関する有意水準を決める(今回は5%)
  6. 自由度、カイ二乗値、有意水準カイ二乗分布表を使ってサイコロが歪んでいるかどうかの判断をする

上の手順を参考に、実際に自分でも問題を解いてみようと思います。
以下のような問題を定義してみました。

問題

あるグループの血液型分布は下のようなものになった。

血液型 人数
A型 18人
B型 13人
0型 14人
AB型 5人

これは日本全体の血液型分布と異なると言えるか。
日本全体の血液型分布は「A型40%、B型20%、O型30%、AB型10%」と仮定、有意水準は5%とする。

回答

まずは期待度数を算出します。
日本全体の分布は「A型40%、B型20%、O型30%、AB型10%」なので期待度数は以下のようになります。

血液型 人数
A型 20人
B型 10人
0型 15人
AB型 5人

それぞれの血液型の (観測結果 - 期待度数)の2乗 / 期待度数 は以下のようになります。

血液型 人数
A型 0.2
B型 0.9
0型 0.67
AB型 0

カイ二乗値はそれらを足した数なので、1.77になります。
そして自由度は4-1で3となります。

カイ二乗分布表によると、自由度3で5%の確率の値は7.81になります。
今回算出したカイ二乗値はそれより低いので、このグループの血液型分布は日本全体の血液型分布と同じだと言えそうです。

ChameleonでUIColorを便利に使う

Chameleonというライブラリを試してみました。

github.com

インストー

CocoaPodsでインストールします。

pod "ChameleonFramework"

使い方

UIColorに多くのフラットカラーが追加されます。

import UIKit
import ChameleonFramework

class ViewController: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()
        
        [
            UIColor.flatRed()!,
            UIColor.flatLime()!,
            UIColor.flatPlum()!
            ].enumerated().forEach {
                
                let colorView = UIView(frame: CGRect(x: 100, y: 100 + 30 * $0.offset, width: 100, height: 30))
                colorView.backgroundColor = $0.element
                view.addSubview(colorView)
        }
    }
}

色は以下のようなものです。

f:id:llcc:20170626233309p:plain

色の明度変更をする事もできます。

import UIKit
import ChameleonFramework

class ViewController: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()
        
        let darkView = UIView(frame: CGRect(x: 100, y: 100, width: 100, height: 30))
        darkView.backgroundColor = UIColor.red.darken(byPercentage: 0.5)
        view.addSubview(darkView)
        
        let lightView = UIView(frame: CGRect(x: 100, y: 130, width: 100, height: 30))
        lightView.backgroundColor = UIColor.red.lighten(byPercentage: 0.5)
        view.addSubview(lightView)
    }
}

下のように明るい赤と暗い赤を取得できます。

f:id:llcc:20170626233718p:plain

グラデーションも簡単に実装できます。

import UIKit
import ChameleonFramework

class ViewController: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()
        
        let colorView = UIView(frame: CGRect(x: 100, y: 130, width: 100, height: 30))
        colorView.backgroundColor = UIColor(gradientStyle: .leftToRight, withFrame: CGRect(x: 100, y: 100, width: 100, height: 30), andColors: [UIColor.red, UIColor.blue, UIColor.purple])
        view.addSubview(colorView)
    }
}

以下のように赤、青、紫のグラデーションを表現できました。

f:id:llcc:20170626233958p:plain

complementaryFlatColorOfを使うとフラットな補色を取得する事ができます。

import UIKit
import ChameleonFramework

class ViewController: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()
        
        let colorView = UIView(frame: CGRect(x: 100, y: 130, width: 100, height: 30))
        colorView.backgroundColor = UIColor(complementaryFlatColorOf: UIColor.red)
        view.addSubview(colorView)
    }
}

引数に赤を渡したら、下のようにフラットな緑色が返ってきました。

f:id:llcc:20170626234546p:plain

色に合わせて白 or 黒を返してくれるメソッドもあります。
黄色だったら黒・赤だったら白など、その色と対照的な色を返してくれます。
色の上に乗せる文字色を決めるのに便利そうです。

import UIKit
import ChameleonFramework

class ViewController: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()
        
        let colorView = UIView(frame: CGRect(x: 100, y: 130, width: 100, height: 30))
        colorView.backgroundColor = UIColor(contrastingBlackOrWhiteColorOn: UIColor.yellow, isFlat: true)
        view.addSubview(colorView)
    }
}

他にも画像の特定座標の色取得など、便利な機能が揃っているようです。