Swiftで配列にuniqをかけるやつ

タイトル通り。 メソッド名は標準のsortInPlaceに合わせた感じにした。

一応使い方

// 引数なし版

var numbers = [1, 2, 3, 2, 3, 4]

let uniqHoges = numbers.uniq()
print(uniqHoges)
// => [1, 2, 3, 4]

print(numbers)
// => [1, 2, 3, 2, 3, 4]

numbers.uniqInPlace()
print(numbers)
// => [1, 2, 3, 4]


// 引数に変換関数を渡す版

struct Fuga {
    let id:  String
    let foo: Int
}

var fugas = [
    Fuga(id: "a", foo: 1),
    Fuga(id: "b", foo: 1),
    Fuga(id: "a", foo: 2)
]

let uniqFugas = fugas.uniqBy { $0.id }
print(uniqFugas)
// => [Fuga(id: "a", foo: 1), Fuga(id: "b", foo: 1)]

print(fugas)
// => [Fuga(id: "a", foo: 1), Fuga(id: "b", foo: 1), Fuga(id: "a", foo: 2)]

fugas.uniqInPlaceBy { $0.id }
print(fugas)
// => [Fuga(id: "a", foo: 1), Fuga(id: "b", foo: 1)]

追記

そういえば、NSOrderedSetに変換してからArrayに戻すスタイルでも実装できる。 どっちが効率良いのかは見ていない。

Xcodeのプロジェクトをリネームする

ほぼ適当な作業メモ。 http://qiita.com/kimi_dropc/items/fa860b0193fa4589a7e0 を見たけどディレクトリ名とか変わらずに残ったから完全に置換するやり方を残しておく。

1. Xcodeに変えてもらう

プロジェクト設定の Identity and Type の Name に新しいプロジェクト名を入れる。

2. 残党狩り

zsh前提。

$ perl -pi -e 's/OldName/NewName/g' **/*.swift **/*.pbxproj Podfile
$ zmv '(*)OldName(*)' '$1NewName$2'

3. CocoaPodsを入れなおす

$ pod install

4. 不死身のやつらを手動でリネーム

  • Manage Schemes... からNewNameに変える
  • ディレクトリ名を変える

that's all

たしかこんな感じでリネームできた。

AutoLayoutで状況に応じた制約を適用する術

やりたいこと

  • 2つのViewを条件によって1つ表示したり2つ表示したり
  • 1つの場合は、親Viewのサイズいっぱいで
  • 2つの場合は、良い感じにマージンを効かせて横並びに同じサイズで親Viewいっぱいに

サンプルコード

レイアウトはコードで書こうマンなのでサンプルコードもコードにて。 SnapKitにどっぷり。 Interface Builderを使う場合は、矛盾する制約をつけつつデフォルト状態を表現する制約意外を無効にしておきつつ、各制約をアウトレットに繋ぐ感じになるはず。

import UIKit
import SnapKit

class ViewController: UIViewController {
    let hogeView = HogeView()

    override func viewDidLoad() {
        super.viewDidLoad()
        self.hogeView.show1()  // どちらか
        self.hogeView.show2()  // 片方を適用
    }

    override func loadView() {
        super.loadView()
        self.view.backgroundColor = UIColor.whiteColor()

        self.view.addSubview(hogeView)
        self.hogeView.snp_makeConstraints { (make) -> Void in
            make.left.top.equalTo(self.view).offset(32)
            make.right.bottom.equalTo(self.view).inset(32)
        }
    }
}

class HogeView: UIView {
    let view1 = UIView()
    let view2 = UIView()

    private var constraints1: [Constraint] = []
    private var constraints2: [Constraint] = []

    init() {
        super.init(frame: CGRectZero)

        self.addSubview(self.view1)
        self.addSubview(self.view2)
        self.view1.backgroundColor = UIColor.greenColor()
        self.view2.backgroundColor = UIColor.redColor()

        // 1個の場合
        self.view1.snp_makeConstraints { (make) -> Void in
            self.constraints1.append(make.left.right.top.bottom.equalTo(self).constraint)
        }
        self.view2.snp_makeConstraints { (make) -> Void in
            self.constraints1.append(make.right.bottom.equalTo(self).constraint)  // サイズが0だから適当なところに置く
            self.constraints1.append(make.width.height.equalTo(0).constraint)
        }

        // 2個の場合
        self.view1.snp_makeConstraints { (make) -> Void in
            self.constraints2.append(make.left.top.bottom.equalTo(self).constraint)
            self.constraints2.append(make.height.equalTo(self).constraint)
        }
        self.view2.snp_makeConstraints { (make) -> Void in
            self.constraints2.append(make.right.top.bottom.equalTo(self).constraint)
            self.constraints2.append(make.left.equalTo(self.view1.snp_right).offset(4).constraint)
            self.constraints2.append(make.width.height.equalTo(self.view1).constraint)
        }
    }

    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    func show1() {
        for constraint in self.constraints1 {
            constraint.activate()
        }
        for constraint in self.constraints2 {
            constraint.deactivate()
        }
    }

    func show2() {
        for constraint in self.constraints1 {
            constraint.deactivate()
        }
        for constraint in self.constraints2 {
            constraint.activate()
        }
    }
}

サンプル動作

1個の場合

f:id:mihyaeru21:20151028003921p:plain

2個の場合

f:id:mihyaeru21:20151028003932p:plain

解説的やつ

2パターンの制約を作っておいて、条件によって有効にする方を切り替える感じのアプローチ。 それだけ。

2個の場合の制約の説明をすると、view1の横幅はview2で設定するから設定しない。 view2は縦横幅がview1と同じで、さらにview1とview2の間には4のスペースが入るので、良い感じに横幅を2分割しつつ間にスペースが入るという寸法。

まとめ

いちいち制約を配列にぶち込んで、それを後でループして頑張る、というスマートではない方法だけど要件は満たす事ができた。 もっとスマートな方法があったら知りたい。

追記

矛盾する制約を設定する前に全部deactivateしておかないと警告が出まくっていた。 こんなextensionを作って self.constraints1.deactivateAll() とかして事なきを得た。

extension Array where Element: Constraint {
    func activateAll() {
        for constraint in self {
            constraint.activate()
        }
    }

    func deactivateAll() {
        for constraint in self {
            constraint.deactivate()
        }
    }
}

今回の例みたいな規模なら、全ての制約を入れ替えることなく一部の制約だけで対処できるはず。 条件によって配置が大幅に変わる場合は今回の方式でやるのが良いかなと。

Slackで人系の絵文字を一気に全色吐き出す君を作った

gist.github.com

クソみたいに雑なスクリプト。 こんな感じで出力される。

$ skintone.pl pray
:pray: :pray::skin-tone-2: :pray::skin-tone-3: :pray::skin-tone-4: :pray::skin-tone-5: :pray::skin-tone-6:

コピペするのはだるいから pbcopy に食わせてあげるのが楽だと思う。

$ skintone.pl +1 | pbcopy

これで雑に盛大な感じでチャットを盛り上げられる!

f:id:mihyaeru21:20151027232009p:plain

Xcode7でInterface Builderを使わずに開発する

概要

  • ViewはSwift(or Objective-C)のコードで組み立てる
  • 生のAutoLayoutのコードは辛いからSnapKitを使う

まずやること

下記をやればInterface Builderから開放される。

  • Main.storyboardを消す
  • プロジェクト設定の Main Interface を空欄にする
  • Launch Screen File は残しておく (これが設定されていないと画面引き伸ばしの設定になっちゃう)
  • AppDelegateのエントリポイントを手動で設定する
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {

    self.window = UIWindow(frame: UIScreen.mainScreen().bounds)
    self.window?.rootViewController = ViewController()
    self.window?.makeKeyAndVisible()

    return true
}

適当にViewを組み立ててみる

HogeViewクラスを適当に追加する。

import UIKit
import SnapKit

class HogeView: UIView {
    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    init() {
        super.init(frame: CGRectZero)  // must call designated initializer
        self.backgroundColor = UIColor.whiteColor()

        let wrapper  = UIView()
        let element1 = UIView()
        let element2 = UIView()
        let label    = UILabel()

        self.addSubview(wrapper)
        wrapper.addSubview(element1)
        wrapper.addSubview(element2)
        element2.addSubview(label)

        wrapper.backgroundColor  = UIColor.cyanColor()
        element1.backgroundColor = UIColor.redColor()
        element2.backgroundColor = UIColor.greenColor()
        label.backgroundColor    = UIColor.lightGrayColor()

        label.text = "hoge"

        wrapper.snp_makeConstraints { (make) -> Void in
            make.left.equalTo(self).inset(8)
            make.right.equalTo(self).inset(8)
            make.centerY.equalTo(self)
            make.height.equalTo(self).dividedBy(2)
        }

        element1.snp_makeConstraints { (make) -> Void in
            make.right.equalTo(wrapper).inset(8)
            make.bottom.equalTo(wrapper)
            make.width.equalTo(50)
            make.height.equalTo(wrapper).dividedBy(2)
        }

        element2.snp_makeConstraints { (make) -> Void in
            make.left.equalTo(wrapper).inset(8)
            make.right.equalTo(element1.snp_left)
            make.top.equalTo(wrapper).inset(8)
            make.bottom.equalTo(element1.snp_top)
        }

        label.snp_makeConstraints { (make) -> Void in
            make.center.equalTo(element2)
        }
    }
}

ViewControllerの方ではこんなで。

import UIKit
import SnapKit

class ViewController: UIViewController {
    override func loadView() {
        self.view = HogeView()
    }
}

こんな感じになる。

f:id:mihyaeru21:20151007225251p:plain

f:id:mihyaeru21:20151007224855p:plain

万歳!!!

AutoLayoutを脳内でプレビューできる場合はかなりやりやすいし、再利用もしやすいし、マージンとかを変数で管理することも簡単。最高!!

またコードでレイアウトを実装する時代に戻ろうではないか!!!!