hachinoBlog

hachinobuのエンジニアライフ

NSLayoutConstraintを使ってAutoLayoutをコードで書く

背景

AutoLayoutをxib上で使っているのだけれどxibでは表現できるAutoLayoutに限界があると思いコードで書く方法を調べたのでメモ。

サンプル

1.self.viewから左20,上20のマージンをとった位置にwidth100,height100の赤いViewを配置

    UIView *redView = [[UIView alloc] init];
    redView.backgroundColor = [UIColor redColor];
    redView.translatesAutoresizingMaskIntoConstraints = NO;
    [self.view addSubview:redView];
    NSLayoutConstraint *redLeftConstraint = [NSLayoutConstraint constraintWithItem:redView
                                                                         attribute:NSLayoutAttributeLeft
                                                                         relatedBy:NSLayoutRelationEqual
                                                                            toItem:self.view
                                                                         attribute:NSLayoutAttributeLeft
                                                                        multiplier:1
                                                                          constant:20];
    
    NSLayoutConstraint *redTopConstraint = [NSLayoutConstraint constraintWithItem:redView
                                                                     attribute:NSLayoutAttributeTop
                                                                     relatedBy:NSLayoutRelationEqual
                                                                        toItem:self.topLayoutGuide
                                                                     attribute:NSLayoutAttributeTop
                                                                    multiplier:1
                                                                      constant:20];
    
    NSLayoutConstraint *redWidthConstraint = [NSLayoutConstraint constraintWithItem:redView
                                                                       attribute:NSLayoutAttributeWidth
                                                                       relatedBy:NSLayoutRelationEqual
                                                                          toItem:nil
                                                                       attribute:NSLayoutAttributeNotAnAttribute
                                                                      multiplier:1
                                                                        constant:100];
    
    NSLayoutConstraint *redHeightConstraint = [NSLayoutConstraint constraintWithItem:redView
                                                                        attribute:NSLayoutAttributeHeight
                                                                        relatedBy:NSLayoutRelationEqual
                                                                           toItem:nil
                                                                        attribute:NSLayoutAttributeNotAnAttribute
                                                                       multiplier:1
                                                                         constant:100];
    
    [self.view addConstraints:@[redLeftConstraint, redTopConstraint, redWidthConstraint, redHeightConstraint]];

f:id:hachinobu:20141103191352p:plain

f:id:hachinobu:20141103191351p:plain

2.self.viewのbottom部分に幅=端末幅,高さ40の青いView配置

    UIView *blueView = [[UIView alloc] init];
    blueView.backgroundColor = [UIColor blueColor];
    blueView.translatesAutoresizingMaskIntoConstraints = NO;
    [self.view addSubview:blueView];
    NSLayoutConstraint *blueLeftConstraint = [NSLayoutConstraint constraintWithItem:blueView
                                                                             attribute:NSLayoutAttributeLeft
                                                                             relatedBy:NSLayoutRelationEqual
                                                                                toItem:self.view
                                                                             attribute:NSLayoutAttributeLeft
                                                                            multiplier:1
                                                                              constant:0];
    NSLayoutConstraint *blueRightConstraint = [NSLayoutConstraint constraintWithItem:blueView
                                                                              attribute:NSLayoutAttributeRight
                                                                              relatedBy:NSLayoutRelationEqual
                                                                                 toItem:self.view
                                                                              attribute:NSLayoutAttributeRight
                                                                             multiplier:1
                                                                               constant:0];
    NSLayoutConstraint *blueHeightConstraint = [NSLayoutConstraint constraintWithItem:blueView
                                                                            attribute:NSLayoutAttributeHeight
                                                                            relatedBy:NSLayoutRelationEqual
                                                                               toItem:nil
                                                                            attribute:NSLayoutAttributeNotAnAttribute
                                                                           multiplier:1
                                                                             constant:40];
    NSLayoutConstraint *blueTopConstraint = [NSLayoutConstraint constraintWithItem:blueView
                                                                        attribute:NSLayoutAttributeTop
                                                                        relatedBy:NSLayoutRelationEqual
                                                                           toItem:self.view
                                                                        attribute:NSLayoutAttributeBottom
                                                                        multiplier:1
                                                                          constant:-blueHeightConstraint.constant];
    [self.view addConstraints:@[blueLeftConstraint, blueRightConstraint, blueTopConstraint, blueHeightConstraint]];

f:id:hachinobu:20141103202832p:plain

f:id:hachinobu:20141103202833p:plain

3.self.viewのcenter座標に幅=self.view.frame.width/2 高さ=self.view.frame.height/2 の黄色Viewを配置

    UIView *yellowView = [[UIView alloc] init];
    yellowView.backgroundColor = [UIColor yellowColor];
    yellowView.translatesAutoresizingMaskIntoConstraints = NO;
    [self.view addSubview:yellowView];
    NSLayoutConstraint *yellowCenterXConstraint = [NSLayoutConstraint constraintWithItem:yellowView
                                                                            attribute:NSLayoutAttributeCenterX
                                                                            relatedBy:NSLayoutRelationEqual
                                                                               toItem:self.view
                                                                            attribute:NSLayoutAttributeCenterX
                                                                              multiplier:1
                                                                                constant:0];
    NSLayoutConstraint *yellowCenterYConstraint = [NSLayoutConstraint constraintWithItem:yellowView
                                                                               attribute:NSLayoutAttributeCenterY
                                                                               relatedBy:NSLayoutRelationEqual
                                                                                  toItem:self.view
                                                                               attribute:NSLayoutAttributeCenterY
                                                                              multiplier:1
                                                                                constant:0];
    NSLayoutConstraint *yellowWidthConstraint = [NSLayoutConstraint constraintWithItem:yellowView
                                                                             attribute:NSLayoutAttributeWidth
                                                                             relatedBy:NSLayoutRelationEqual
                                                                                toItem:self.view
                                                                             attribute:NSLayoutAttributeWidth
                                                                            multiplier:.5
                                                                              constant:0];
    NSLayoutConstraint *yellowHeightConstraint = [NSLayoutConstraint constraintWithItem:yellowView
                                                                              attribute:NSLayoutAttributeHeight
                                                                              relatedBy:NSLayoutRelationEqual
                                                                                 toItem:self.view
                                                                              attribute:NSLayoutAttributeHeight
                                                                             multiplier:.5
                                                                               constant:0];
    [self.view addConstraints:@[yellowCenterXConstraint, yellowCenterYConstraint, yellowWidthConstraint, yellowHeightConstraint]];

f:id:hachinobu:20141103203343p:plain

f:id:hachinobu:20141103203344p:plain

共通する部分としてViewのtranslatesAutoresizingMaskIntoConstraintsプロパティは必ずNOにすること。

これをしないとAutolayoutが適用されない。

Viewを作成する際にinitWithFrame:で幅と高さを指定しても意味はないのでNSLayoutConstraintのメソッドで幅と高さの制約を作る必要がある。

組み合わせや使い方によっては柔軟なものができそうであるが、それにしてもNSLayoutConstraintはマゾい

なので今度Masonryを試してみようと思う。

iOS7とiOS8でUIScreenのboundsのサイズが違うので対応する

背景

iOS8からUIScreenのboundsを取得すると端末の向きに応じてwidthとheightの値が変更されるようになりLandscape対応でちょっと困ったのでメモ

iOS7とiOS8で差分を埋める

+ (CGSize)mainScreenSize 
{
    CGSize screenSize = [UIScreen mainScreen].bounds.size;
    if ((NSFoundationVersionNumber <= NSFoundationVersionNumber_iOS_7_1) &&  UIInterfaceOrientationIsLandscape([UIApplication sharedApplication].statusBarOrientation)) {
        return CGSizeMake(screenSize.height, screenSize.width);
    }
    return screenSize;
}

これでiOS7でもiOS8と同様のサイズを返すです。

重複件数まで取得できるNSCountedSet

背景

例えばこんな問題があったとする。

インプットした文字列の単語の重複件数を求めなさい。

例) banana

答) b: 1 a: 3 n: 2

この問題を何か効率的に処理できる方法はないかと考えたときにNSCountedSetを見つけたので使い方をメモ。

使い方

NSCountedSetはNSMutableSetのサブクラスなので当然、格納した際に重複しているものは取り除かれるのだが、取り除く際に重複のcount数を保持している。

なので上記の問題に対応するコードをNSCountedSetを使用して書くとこうなる。

    NSString *text = @"banana";
    NSCountedSet *countSet = [NSCountedSet set];
    int total = [text length];
    for (int i = 0; i < total; i++) {
        [countSet addObject:[text substringWithRange:NSMakeRange(i, 1)]];
    }
    for (NSString *word in countSet) {
        NSLog(@"%@:%@", word, @([countSet countForObject:word]));
    }

出力結果

b:1
a:3
n:2

ちょっと便利な気がするー。

git clone --recursive をするとサブモジュールも一緒に持ってきてくれる

背景

SDWebImageのDemoを動かしたくてgit cloneして動かしたらfile not foundでエラーになった。

解決方法

SDWebImageはサブモジュールを使用しているので、それも一緒に入れてやる必要がある。

なのでgit clone する際に --recursive オプションをつけてやるとサブモジュールも一緒に入れてくれる。

git clone --recursive https://github.com/rs/SDWebImage.git

Cloning into 'SDWebImage'...
remote: Reusing existing pack: 2754, done.
remote: Total 2754 (delta 0), reused 0 (delta 0)
Receiving objects: 100% (2754/2754), 3.64 MiB | 294.00 KiB/s, done.
Resolving deltas: 100% (1600/1600), done.
Checking connectivity... done.
Submodule 'Vendors/libwebp' (http://git.chromium.org/webm/libwebp.git) registered for path 'Vendors/libwebp'
Cloning into 'Vendors/libwebp'...
remote: Counting objects: 11978, done.
remote: Compressing objects: 100% (3941/3941), done.
remote: Total 11978 (delta 9284), reused 10570 (delta 8029)
Receiving objects: 100% (11978/11978), 4.53 MiB | 431.00 KiB/s, done.
Resolving deltas: 100% (9284/9284), done.
Checking connectivity... done.
Submodule path 'Vendors/libwebp': checked out '68e7901da53cbda6ec93ddf93e039346d3c6a531'

--recursiveを使っていきましょー。

SwiftのArrayとDictionaryの使い方

背景

基本的な言語仕様はふんわりと学んだので、今後多用するであろうArrayとDictionaryの使い方を調べたのでメモ。

Array サンプルコード

// immutableなString型の配列
let names: String[] = ["name5", "name2", "name1", "name3"]

//各データを取得
let name1 = names[0] //name5
let name2 = names[1] //name2
let name3 = names[2] //name1
let name4 = names[3] //name3

//各件数
names.capacity // 4
names.startIndex // 0
names.endIndex // 4
names.count // 4


// mutableなString型の配列
var programLang = ["Objective-C", "Java"]

// データ追加
programLang.append("Swift") // [Objective-C, Java, Swift]
programLang += "Python" // [Objective-C, Java, Swift, Python]

let addLang = ["Ruby", "PHP"] 
programLang.extend(addLang) // [Objective-C, Java, Swift, Python, Ruby, PHP]
programLang += ["Scala", "JavaScript"] // [Objective-C, Java, Swift, Python, Ruby, PHP, Scala, JavaScript]

programLang.insert("ひまわり", atIndex: 0) // [ひまわり, Objective-C, Java, Swift, Python, Ruby, PHP, Scala, JavaScript]

// 配列の中身を逆順にする
programLang.reverse() // [ひまわり, Objective-C, Java, Swift, Python, Ruby, PHP, Scala, JavaScript]

// 配列内をソート(降順)
sort(programLang) { $0 > $1 } // [ひまわり, Swift, Scala, Ruby, Python, PHP, Objective-C, JavaScript, Java]

// 配列内をソート(昇順)
sort(programLang) { $0 < $1 } // [Java, JavaScript, Objective-C, PHP, Python, Ruby, Scala, Swift, ひまわり]

// programLang配列のインデックスが0,1,2のデータを取得
programLang[0...2] // [Java, JavaScript, Objective-C]

// programLang配列のインデックスが0,1のデータをCとC#にする
programLang[0...1] = ["C", "C#"] // [C, C#, Objective-C, PHP, Python, Ruby, Scala, Swift, ひまわり]

// 先頭にCがつく言語を取得
programLang.filter { $0.hasPrefix("C") } // [C, C#]

// 各言語データの先頭に Language: を付与
programLang.map { "Language: \($0)" } // [Language: C, Language: C#, Language: Objective-C, Language: PHP, Language: Python, Language: Ruby, Language: Scala, Language: Swift, Language: ひまわり]

// 指定インデックスに該当するデータ削除
programLang.removeAtIndex(0) // [C#, Objective-C, PHP, Python, Ruby, Scala, Swift, ひまわり]

// インデックス0,1,2のデータを削除
programLang[0...2] = []  // [Python, Ruby, Scala, Swift, ひまわり]

// 配列の最後に格納されているデータを削除
programLang.removeLast()  // [Python, Ruby, Scala, Swift]

// 全て削除
programLang.removeAll()  // []

// copyとunshare
// mutableな配列
var numbersA: Int[] = [1, 2, 3]
var numbersB: Int[] = numbersA
var numbersC: Int[] = numbersA.copy() // numbersAをcopy

numbersA[0] = 0
println(numbersA)  // [0, 2, 3]
println(numbersB)  // [0, 2, 3]
println(numbersC)  // [1, 2, 3]

// copyしたnumbersBをunshare()
numbersB.unshare()
numbersA[1] = 5
numbersC[0...1] = [6, 7]

println(numbersA)  // [0, 5, 3]
println(numbersB)  // [0, 2, 3] unshareしたので参照データでなくなった
println(numbersC)  // [6, 7, 3]

Objective-CのNSArrayと同じように使えるけど += で配列にデータを追加できたりsortが用意されていたりで柔軟で使いやすい印象。


Dictionaries サンプルコード

// 初期化
var personInfo: Dictionary = ["name": "hachinobu", "age": 28]

// データ追加
personInfo["sex"] = "man"

// データ取得
personInfo["name"] //  hachinobu
personInfo["age"]    //  28
personInfo["sex"]    //  man

// 格納件数
personInfo.count //  3

// keyの配列を取得
let keys: Array = Array(personInfo.keys)  //  [sex, name, age]

// valueの配列を取得
let values: Array = Array(personInfo.values) //  [man, hachinobu, 28]

// Dictionaryにnameというkeyでデータが格納されているかチェック、なければ追加する
if let name = personInfo["name"] {
    println("name is \(name)") //  name is hachinobu
} else {
    personInfo["name"] = "hachinobu"
}

// データ更新
personInfo.updateValue("nishinobu", forKey: "name")  //  [sex: man, name: nishinobu, age: 28]


// 既に存在するkeyならupdateで存在しないkeyであればinsertになる
if let age = personInfo.updateValue(29, forKey: "age") {
    println("update")
} else {
    println("insert") 
}

// 追加
personInfo["height"] = 171 //  [age: 29, sex: man, name: nishinobu, height: 171]

// 指定したkeyのデータを削除
personInfo.removeValueForKey("height") //  [age: 29, sex: man, name: nishinobu]

// personInfoのようにvalueの型がOptionalでない場合はvalueにnilを入れると削除扱い
personInfo["name"] = nil //  [age: 29, sex: man]

// 下記のようにvalueの型がOptionalの場合にvalueにnilを入れるとnilが代入される
var optionalDict: Dictionary<String, NSObject?> = ["key1": "A", "key2": "B"]
optionalDict["key1"] = nil // [key2: B, key1: nil]

valueにnil入れて削除とか面白い

valueの型にOptionalを指定すると挙動が変わるので注意が必要


addSubviewやremoveFromSuperviewをトリガーにする方法

背景

addSubviewやremoveFromSuperviewしたタイミングでViewの処理をしたかったので色々調べてたら出てきたのでメモ。

方法

willMoveToSuperview:didMoveToSuperviewメソッドを使用すれば良い。

willMoveToSuperview:

自身(UIView)が親Viewに追加される直前に呼ばれる。

引数のnewSuperviewには親Viewがはいってくる。

追加でなく削除される直前にもこのメソッドは呼ばれる。

削除の場合は引数のnewSuperviewがnilになる。

didMoveToSuperview

自身(UIView)が親Viewに追加された直後に呼ばれる。

追加でなく削除される直後にも呼ばれる。

サンプルコード

該当のメソッドを使った簡単なサンプルコード

UIViewMoveEventSample

WWDCで新言語 Swiftが発表された日にObjective-Cの日記を書いている…

何だか乗り遅れた感が…

Swift勉強するぞー!

CoreDataで集計関数を使う

背景

前回のNSExpressionを使って今度はCoreDataで集計関数を使用する方法を調べたのでメモ。

サンプル

下記のデータ構造とデータを格納しているSeminarsテーブルがあると仮定する。

Seminars

date seminar charge
2014/05/01 SeminarA 1,000
2014/05/02 SeminarB 1,500
2014/05/01 SeminarC 500
2014/05/03 SeminarD 2,000
2014/05/02 SeminarE 0
2014/05/01 SeminarF 0

日付ごとのセミナー件数を取得してみる。

NSEntityDescription* entity = [NSEntityDescription entityForName:@"Seminars"
                                          inManagedObjectContext:_managedObjectContext];

//集計対象カラムseminar
NSExpression *keyPathExpression = [NSExpression expressionForKeyPath:@"seminar"];
//集計関数countを指定
NSExpression *countExpression = [NSExpression expressionForFunction:@"count:"
                                                          arguments:@[keyPathExpression]];

//集計式の対象(NSExpressionDescription)
NSExpressionDescription *expressionDescription = [[NSExpressionDescription alloc] init];
[expressionDescription setName:@"seminarCount"];
[expressionDescription setExpression:countExpression];
[expressionDescription setExpressionResultType:NSInteger32AttributeType];

[searchFetchRequest setPropertiesToFetch:@[@"date", expressionDescription]];
//Group By
[searchFetchRequest setPropertiesToGroupBy:@[@"date"]];

NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:@"date" ascending:YES];
[searchFetchRequest setSortDescriptors:@[sortDescriptor]];
[searchFetchRequest setResultType:NSDictionaryResultType];
NSArray *fetchedObjects = [_managedObjectContext executeFetchRequest:searchFetchRequest error:nil];

NSLog(@"出力結果:%@", fetchedObjects);

出力結果

({
    date = "2014/05/01";
    seminarCount = 3;
},
{
    date = "2014/05/02";
    seminarCount = 2;
},
{
    date = "2014/05/03";
    seminarCount = 1;
})

ごちゃごちゃと面倒な印象。

やはりMagicalRecordを使うべしですかね。