なんとかするから、なんとかなる

エンジニア関係のことを書きます

iOS AutoLayoutをコードから設定する方法

English version below

はじめに

AutoLayoutが発表された当初、AutoLayoutをコードから設定するのは面倒でした。

NSLayoutConstraint(item: button, attribute: .leading, relatedBy: .equal, toItem: self.view, attribute: .leadingMargin, multiplier: 1.0, constant: 0.0).isActive = true

つらい。

そこでよくSnapKitというOSSを使っていました。

github.com

しかし、最近はOSSを使わなくとも簡単に設定することができるため、その方法を備忘録として

やり方

viewの下端にUIButtonを設置したい場合は、次のように設定します。

コード

let button = UIButton()
self.view.addSubview(button)
button.translatesAutoresizingMaskIntoConstraints = false
button.centerXAnchor.constraint(equalTo: self.view.centerXAnchor).isActive = true
button.heightAnchor.constraint(equalTo: self.view.heightAnchor, multiplier: 0.06).isActive = true
button.widthAnchor.constraint(equalTo: self.view.widthAnchor, multiplier: 0.7).isActive = true
button.bottomAnchor.constraint(equalTo: self.view.bottomAnchor, constant: -20).isActive = true

注意点

addSubview()の順番

addSubviewでSubviewに登録する場合にconstraitを設定すると落ちます。

translatesAutoresizingMaskIntoConstraintsの設定忘れ

translatesAutoresizingMaskIntoConstraintsをfalseに設定しないと制約が反映されません。

isActive = trueの設定忘れ

isActiveを設定しないと制約は有効になりません。

SafeAreaの設定方法

セーフエリアの設定には、safeAreaLayoutGuideを使用します。

if #available(iOS 11, *) {
    button.bottomAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.bottomAnchor, constant: -20).isActive = true
} else {
    button.bottomAnchor.constraint(equalTo: self.view.bottomAnchor, constant: -20).isActive = true
}

viewに対する比率を指定して空き(マージン)を作る

AutoLayoutを使用していると、例えばUIButtonをviewの高さ1/3のところに設定したい。

など、あるViewに対する比率分空けたいなど比率を指定したいこともあります。

その場合はUILayoutGuideを使います。

空欄のUIViewを置くという方法もありますが、私はあまり好きではないです。

次のコードはUIButton(self.button)をViewの下部から高さ*0.2空けて表示するときのUILayoutGuideです。

let spacer = UILayoutGuide()
self.view.addLayoutGuide(spacer)
spacer.heightAnchor.constraint(equalTo: self.view.heightAnchor, multiplier: 0.5).isActive = true
spacer.bottomAnchor.constraint(equalTo: self.view.bottomAnchor).isActive = true
spacer.topAnchor.constraint(equalTo: button.bottomAnchor).isActive = true

最後に

最後にコード全体と示します。 UIButtonを宣言して、Autolayoutを設定するまでのコードです。

lazy var mybutton: UIButton = {
    let button = UIButton()
    self.view.addSubview(button)
    button.translatesAutoresizingMaskIntoConstraints = false
    button.centerXAnchor.constraint(equalTo: self.view.centerXAnchor).isActive = true
    button.heightAnchor.constraint(equalTo: self.view.heightAnchor, multiplier: 0.06).isActive = true
    button.widthAnchor.constraint(equalTo: self.view.widthAnchor, multiplier: 0.7).isActive = true
        
    let spacer = UILayoutGuide()
    self.view.addLayoutGuide(spacer)
    spacer.heightAnchor.constraint(equalTo: self.view.heightAnchor, multiplier: 0.5).isActive = true
    spacer.bottomAnchor.constraint(equalTo: self.view.bottomAnchor).isActive = true
    spacer.topAnchor.constraint(equalTo: button.bottomAnchor).isActive = true
     
    return button
} ()

iOS How to use the Decodable

Introdution

This article show the basically usage of Decodable Protocol.  

What's the Decodable

The Decodable give us an easy way to convert the JSON Data type to Class or Struct.   Thanks for the Decoable protocol, developer could access each member value easily without any string key.   String Key oftem make us a typo.  It sometimes annoyed me ; )  

Let's try it!

Let's try to define the Docodable Struct

struct SomeStruct: Decodable {
    var value: String
    var version: Int
    
    enum CodingKeys: String, CodingKey {
        case value
        case version = "app_version"
    }
}

 

Let's try to Decode the JSON data type

let json: Data = """
{
    "value": "GoodString",
    "app_version": 5
}
""".data(using: .utf8)!
let someStruct = try? JSONDecoder().decode(SomeStruct.self, from: json)

 

Details

At firts, apply the Decodable Protocol to Struct type.   Implement the enum of CodingKeys.   That's All!   CodingKeys need to include each values's name.   It the values name is different from the JSON Key value, you can declear the raw value in declaring of CodingKeys enum.   Important thing is that each values need declear in var instead of let.   If you declear the value in let, the decoding process allways return nil. ;)   If the some JSON key has optional key, it's ok to clear the member value with optional "?" .  

struct SomeStruct: Decodable {
    var value: String
    var version: Int
    var exception: Int?
    
    enum CodingKeys: String, CodingKey {
        case value
        case version = "app_version"
    }
}

 

Decode the JSON which has another struct.

Let's try to decode the JSON Data type which has another struct.   In this case, SomeStruct has InnerStruct Array.  

Declear the Decodable

 

struct SomeStruct: Decodable {
    var value: String
    var version: Int
    var inners: [InnerStruct]
    
    enum CodingKeys: String, CodingKey {
        case value
        case version = "app_version"
        case inners = "inner_structs"
    }
}
 
struct InnerStruct: Decodable {
    var index: Int
    var name: String
    
    enum CodingKeys: String, CodingKey {
        case index = "inner_index"
        case name = "inner_name"
    }
}

  If you would like to clear the InnerStruct as is, it's OK to clear it directly.

struct SomeStruct: Decodable {
    var value: String
    var version: Int
    var inner: InnerStruct
    
    enum CodingKeys: String, CodingKey {
        case value
        case version = "app_version"
        case inners= "inner_struct"
    }
}

 

Let's try to Decode the JSON data type

 

let json: Data = """
{
    "value": "GoodString",
    "app_version": 5,
    "inner_structs": [{
        "inner_index": 1,
        "inner_name": "myName"
    }]
}
""".data(using: .utf8)!
let someStruct = try? JSONDecoder().decode(SomeStruct.self, from: json)

 

Wrap up!

The decodable is easy and useful !!

iOS Decodableの基本的な使い方

English version below

はじめに

今回はSwift4 で追加されたDecodableの使い方について紹介します。  

Decodableとは

API Requestなどで返ってくるJSONのData型を任意のクラスや構造体に変換する際に便利です。

これまではSwiftyJSONなどライブラリーを使ってJSONの構造体にアクセスすることが多かったと思います。

しかし、そのままSwiftyJSONを使う場合、

  • 連想配列Dictionary に対して文字列を渡すことでタイポの危険性を伴う
  • 意図しないJSONでもJSONであれば問題無い

など、もやもやが僕の中にはありました。

Decodableを使うことで決まったJSONを決まったクラス/構造体にしか変換しないので、とてもわかりやすくて好きです。

Decodableを使って見る

構造体に対してDecodableのProtocolを適用する。

 

struct SomeStruct: Decodable {
    var value: String
    var version: Int
    
    enum CodingKeys: String, CodingKey {
        case value
        case version = "app_version"
    }
}

 

試しにデコードして見る

 

let json: Data = """
{
    "value": "GoodString",
    "app_version": 5
}
""".data(using: .utf8)!
let someStruct = try? JSONDecoder().decode(SomeStruct.self, from: json)

 

詳細

構造体に対して、Decodableの適用を宣言。

CodingKeysという列挙体を宣言すると完了です。

CodingKeysはrawValueがStringの列挙体で、JSONの各Keyがどの変数に対応しているのかを宣言します。

CodingKeysのrawValueが変数名と等しい場合は、変数名をそのまま列挙体でも使用、異なる場合はrawValueに対してKey名を対応させます。

構造体の各変数はvarで宣言しないと、Decodeに失敗しnilが返ってきます。

hopita.hatenablog.com

JSONのKeyで存在の有無がOptionalの場合は構造体の変数宣言をOptionalにすればOKです。

struct SomeStruct: Decodable {
    var value: String
    var version: Int
    var exception: Int?
    
    enum CodingKeys: String, CodingKey {
        case value
        case version = "app_version"
    }
}

 

階層のあるJSONをデコードする

次に、階層構造のあるJSONについてです。 上記のSomeStruct の中にInnerStructが配列で存在すること場合、次のように宣言します。  

Decodableの宣言

 

struct SomeStruct: Decodable {
    var value: String
    var version: Int
    var inners: [InnerStruct]
    
    enum CodingKeys: String, CodingKey {
        case value
        case version = "app_version"
        case inners = "inner_structs"
    }
}
 
struct InnerStruct: Decodable {
    var index: Int
    var name: String
    
    enum CodingKeys: String, CodingKey {
        case index = "inner_index"
        case name = "inner_name"
    }
}

  今回はInnerStructは配列ですが、InnerStruct単体であれば[]を外せばOKです。  

デコードする

 

let json: Data = """
{
    "value": "GoodString",
    "app_version": 5,
    "inner_structs": [{
        "inner_index": 1,
        "inner_name": "myName"
    }]
}
""".data(using: .utf8)!
let someStruct = try? JSONDecoder().decode(SomeStruct.self, from: json)

 

おわりに

とても便利です。簡単です。使っていきましょう。

iOS How to wait for the async procedure in XCTest

Introduction

This article shows how to wait for an async procedure like API Request or some closure.

Solution

It is easy to wait an async procedure using "expectation"

Sample Code

let request = SomeRequest()
let expect = expectation(description: "SendMyRequest")
APIClient.send(request: request) { (result) in
    switch result {
    case .success(let response):
        expect.fulfill()
    case .failure(let error):
        XCTFail("Failuer in API Client")
    }
}
waitForExpectations(timeout: 5) { (error) in
    if let error = error {
    // Some error occured like network error.
        print(error)
        XCTFail("ExpectaionTimeOut")
    } else {
    // Finished with no error.
    }
}

Detail

At first, declare the expectation with some identifier.

let expect = expectation(description: "SOME IDENTIFIER FOR THE LOG")

To expect the above valuable.

Call the fulfill method when an async procedure finished.

expect.fulfill()

At last, call the waitForExpectations(timeout:handler:) method with time out duration and completion handler.

waitForExpectations(timeout: 5) { (error) in
    // Do something if you want. If some error happened, the error value is not nil.
}

iOS XCTestで非同期処理の完了を待つ

English version below

はじめに

タイトル通りです。

APIRequestなどXCTestで疎通確認をした際に、非同期処理の完了を待ちたいことはよくあります。

その方法を簡単に紹介

解決方法

expectationを使います。

次のように書いて待つことを宣言

let expect = expectaion(description: "テストログ用の文字列")

上記のexpectが満たされたこと示すために、非同期処理完了時にfulfillを呼びます。

expect.fulfill()

最後にwaitForExpectations(timeout:handler:)でタイムアウト時間などを指定して、完了時の処理をhandlerに渡します。

waitForExpectations(timeout: 5) { (error) in
    // 何かの処理
}

ね。簡単でしょ。

サンプルコード

こんな感じで使います。

let request = SomeRequest()
let expect = expectation(description: "SendMyRequest")
APIClient.send(request: request) { (result) in
    switch result {
    case .success(let response):
        expect.fulfill()
    case .failure(let error):
        XCTFail("Failuer in API Client")
    }
}
waitForExpectations(timeout: 5) { (error) in
    if let error = error {
    //なにかしらエラー発生(タイムアウトなど)
        print(error)
        XCTFail("ExpectaionTimeOut")
    } else {
    //問題なく完了
    }
}