ペンギン村 Tech Blog

技術をこよなく愛するエンジニア集団が在住するペンギン村から、世界へ役立つ(かもしれない)技術情報を発信する技術系ブログです。某アラレちゃんが済む村とは一切関係ありません。んちゃ!

【iOS】APIKitを使ったXML取得

まえがき

初めまして。ペンギン村の通行人k_miyasakaです。

APIKitを使用してGoogle Suggest APIからXMLを取得する方法をまとめました。

結果

"hello"という文字に対するサジェスト結果をパースしてテーブルに表示

xml取得部分はこんな感じのコードになりました。

import APIKit
import SwiftyXMLParser

/* リクエスト */
struct GoogleSuggestionRequest: Request {
    typealias Response = [String]
    var baseURL: URL = URL(string: "https://www.google.com")!
    
    var path: String = "/complete/search"
    
    var method: HTTPMethod = .get
    
    var parameters: Any? = ["q": "hello", "hl": "ja", "output": "toolbar"]
    
    var dataParser: DataParser = MyXMLParser()
    
    func response(from object: Any, urlResponse: HTTPURLResponse) throws -> [String] {
        return object as! [String] // 強気😼
    }
}

/* DataParser実装クラス */
class MyXMLParser: DataParser {
    public var contentType: String? {
        return "application/xml"
    }
    
    public func parse(data: Data) throws -> Any {
        guard let xmlString = String(data: data, encoding: .shiftJIS) else {throw ResponseError.unexpectedObject("文字列にキャストできなかった。")}
        let xml = try SwiftyXMLParser.XML.parse(xmlString)
        let suggestion: [String] = xml["toplevel", "CompleteSuggestion"].map{ $0["suggestion"]}.flatMap{$0.attributes["data"]}
        if suggestion.isEmpty {
            throw ResponseError.unexpectedObject("候補なし")
        }
        return suggestion
    }
}



/* 適当な通信するクラス */
class Connector {
    static func getSuggestion() {
        let request =  GoogleSuggestionRequest()
        Session.send(request){result in
            switch result {
            case .success(let suggestion):
                print(suggestion) // 成功した時の結果
            case .failure(let error):
                print(error) // 通信、パースのエラーはここでハンドリング
            }
        }
    }
}

つまづいたポイント

"https"から始まるURLじゃないと通信できない。

AppleによるATS(App Transport Security)のため、
”http“で始まるURLには原則アクセスできません。
以下のようなエラーログが出力されます。

App Transport Security has blocked a cleartext HTTP (http://) resource load since it is insecure. Temporary exceptions can be configured via your app's Info.plist file.String(data: data, encoding: .shiftJIS)

独自のXMLパーサーを実装する必要がある。

APIKitのRequestプロトコルはdataParserというプロパティを持っています。
(APIKitのDocumentにはたぶん記載がなく、コードを見て知りました。)
明示的に指定しない場合はデフォルトでJSONのパースを実行するようになっています。
(パースに失敗するとRequestクラスのresponseメソッドは実行されません。)

デフォルト実装のままxmlを取得するとパースに失敗し以下のようなエラーとなります。

responseError(Error Domain=NSCocoaErrorDomain Code=3840 "JSON text did not start with array or object and option to allow fragments not set." UserInfo={NSDebugDescription=JSON text

XMLを取得する場合はdataParserプロパティにDataParserプロトコルに準拠した独自のXMLParserを代入する必要があります。

var dataParser: DataParser = MyXMLParser()

また、今回はSwiftyXMLParserというライブラリを使用しXMLをパースしました。

文字エンコーディングに気をつけよう

取得したData型をString型に直す時は文字エンコーディングの形式を指定する必要があります。

String(data: data, encoding: .shiftJIS)

指定するエンコーディングはresponseメソッドの引数のurlResponseに格納されており、 print(urlResponse.textEncodingName)のようにして確認しました。

【iOS】【Swift】太り過ぎなUIViewController.viewDidLoadをスリムにして可読性を爆上げする

前書き

本命/義理チョコを今か今かと待ちわびています、ナガクラ(@nagakuta)です!一ヶ月ぶりの投稿になります!
今回は上級者が開発時に使うといわれる、ちょこっと役立つTipsを紹介します!(バレンタインだけに)

TL;DR

viewDidLoadをスリムにするには

  1. XIBでViewを実装する場合はdidSetを使う
  2. コードでViewを実装する場合はClosureを使う

太り過ぎなviewDidLoad

皆さんは、Viewを実装する際にどの方法でUIパーツの設定を行いますか?(´ε`;)ウーン…

自分は、今回の方法を知るまでは以下の方法を採っていました。

  • XIBでCustom Viewのレイアウトを作成する場合
    • Interface Builderにて設定 or viewDidLoad内でコードによる設定
  • コードでCustom Viewを実装する場合
    • loadViewでCustom Viewを実装、viewDidLoadで細かい設定を実装

しかし、上記の方法では、UIパーツの数が多ければ多いほどviewDidLoadが肥大化してしまい、最悪の場合は何十行もあるviewDidLoadになってしまうこともありましたorz

final class CustomViewController: UIViewController {

    @IBOutlet private weak var firstLabel: UILabel!
    @IBOutlet private weak var secondLabel: UILabel!
    @IBOutlet private weak var thirdLabel: UILabel!
    @IBOutlet private weak var fourthLabel: UILabel!
    @IBOutlet private weak var firstButton: UIButton!
    @IBOutlet private weak var secondButton: UIButton!
    @IBOutlet private weak var thirdButton: UIButton!

    // MARK: - Life Cycle Methods

    override func viewDidLoad() {
        super.viewDidLoad()

        // FirstLabelの設定
        self.firstLabel.text = /* 省略 */
        self.firstLabel.font = /* 省略 */
        self.firstLabel.textColor = /* 省略 */
        self.firstLabel.textAlignment = /* 省略 */
        self.firstLabel.lineBreakMode = /* 省略 */

        // SecondLabelの設定
        self.secondLabel.text = /* 省略 */
        self.secondLabel.font = /* 省略 */
        self.secondLabel.textColor = /* 省略 */
        self.secondLabel.textAlignment = /* 省略 */
        self.secondLabel.lineBreakMode = /* 省略 */

        (中略)

        // FirstButtonの設定
        self.firstButton.setTitle("ボタン1", for: .normal)
        self.firstButton.setTitleColor(UIColor.red, for: .normal)
        self.firstButton.tintColor = /* 省略 */

        // SecondButtonの設定
        self.secondButton.setTitle("ボタン2", for: .normal)
        self.secondButton.setTitleColor(UIColor.blue, for: .normal)
        self.secondButton.tintColor = /* 省略 */

        (省略)
    }

}

(可読性が低い…低すぎるぞ…!😨😨😨)

それでは可読性が低いので、各UIパーツごとに設定用のメソッド(e.g. setupLabel)を実装し、それをviewDidLoadで呼び出す、という方法で回避を試みましたが…

final class CustomViewController: UIViewController {

    (省略)

    // MARK: - Life Cycle Methods

    override func viewDidLoad() {
        super.viewDidLoad()

        self.setupFirstLabel()
        self.setupSecondLabel()

        (中略)

        self.setupFirstButton()
        self.setupSecondButton()

        (省略)
    }

}

// MARK: - Private Methods
extension CustomViewController {

    /// FirstLabelの設定
    private func setupFirstLabel() {
        self.firstLabel.text = /* 省略 */
        self.firstLabel.font = /* 省略 */
        self.firstLabel.textColor = /* 省略 */
        self.firstLabel.textAlignment = /* 省略 */
        self.firstLabel.lineBreakMode = /* 省略 */
    }

    /// SecondLabelの設定
    private func setupSecondLabel() {
        self.secondLabel.text = /* 省略 */
        self.secondLabel.font = /* 省略 */
        self.secondLabel.textColor = /* 省略 */
        self.secondLabel.textAlignment = /* 省略 */
        self.secondLabel.lineBreakMode = /* 省略 */
    }

    (中略)

    /// FirstButtonの設定
    private func setupFirstButton() {
        self.firstButton.setTitle("ボタン1", for: .normal)
        self.firstButton.setTitleColor(UIColor.red, for: .normal)
        self.firstButton.tintColor = /* 省略 */
    }

    /// SecondButtonの設定
    private func setupSecondButton() {
        self.secondButton.setTitle("ボタン2", for: .normal)
        self.secondButton.setTitleColor(UIColor.blue, for: .normal)
        self.secondButton.tintColor = /* 省略 */
    }

    (省略)

}

(何だこれは…やたらメソッドが増えるじゃないか…😥😥😥)

結局のところ、この方法もベストとは言えない方法だと思っていました。

viewDidLoadをシェイプアップする冴えた方法

なんとかviewDidLoadを膨らませず、かつ設定用メソッドの実装以外の方法でUIパーツの設定を行えないか…
その方法を探るべく、インターネットを彷徨っていたところ、一件のページを見つけました。

thatthinginswift.com

その記事中には、

  • viewDidLoadでUIパーツの設定やプロパティの初期化をするのは古臭いし、いい方法じゃないよね
  • Swiftでは、didSetClosureでプロパティの初期化をする方法があるって知ってた?
  • じゃあ、これらを使ってUIパーツの設定を行えばいいんじゃないかな

といったことが書かれていました。

「…え、Closureでプロパティの初期化ができるの!?」と驚いた私がApple公式のSwiftガイドを見ると、たしかにClosureでのプロパティ初期化の方法が載っているではありませんか。

developer.apple.com

なるほど、確かにこの方法をUIパーツに適用すれば、わざわざviewDidLoadでゴリゴリと設定を実装しなくても良くなりそうです。

実際にやってみた

XIBでCustom Viewのレイアウトを実装している場合

XIBでCustom Viewのレイアウトを実装する場合、IBOutletでコードと結びつけてから設定を行います。

その場合はClosureによる初期化ができないので、didSetを利用して設定します。

final class CustomViewController: UIViewController {

    @IBOutlet private weak var firstLabel: UILabel! {
        didSet {
            self.firstLabel.text = /* 省略 */
            self.firstLabel.font = /* 省略 */
            self.firstLabel.textColor = /* 省略 */
            self.firstLabel.textAlignment = /* 省略 */
            self.firstLabel.lineBreakMode = /* 省略 */
        }
    }

    @IBOutlet private weak var secondLabel: UILabel! {
        didSet {
            self.secondLabel.text = /* 省略 */
            self.secondLabel.font = /* 省略 */
            self.secondLabel.textColor = /* 省略 */
            self.secondLabel.textAlignment = /* 省略 */
            self.secondLabel.lineBreakMode = /* 省略 */
        }
    }

    @IBOutlet private weak var thirdLabel: UILabel! {
        didSet {
            (省略)
        }
    }

    @IBOutlet private weak var fourthLabel: UILabel! {
        didSet {
            (省略)
        }
    }

    @IBOutlet private weak var firstButton: UIButton! {
        didSet {
            self.firstButton.setTitle("ボタン1", for: .normal)
            self.firstButton.setTitleColor(UIColor.red, for: .normal)
            self.firstButton.tintColor = /* 省略 */
        }
    }

    @IBOutlet private weak var secondButton: UIButton! {
        didSet {
            self.secondButton.setTitle("ボタン2", for: .normal)
            self.secondButton.setTitleColor(UIColor.blue, for: .normal)
            self.secondButton.tintColor = /* 省略 */
        }
    }

    @IBOutlet private weak var thirdButton: UIButton! {
        didSet {
            (省略)
        }
    }

    // MARK: - Life Cycle Methods

    override func viewDidLoad() {
        super.viewDidLoad()

        (ここで何もしなくていい😄)
    }

}

おお…!これならviewDidLoadが初期化で膨れ上がることもないし、設定用のメソッドを書かなくてもいいし、どのUIパーツにどの設定がされているかが一目瞭然ですね😊😊😊

ちなみに、このdidSetが呼ばれるタイミングはviewDidLoadの前になります。IBOutletプロパティに値がセットされるタイミング(loadViewと同じタイミング…?勉強不足ですみません🙇)で各々のUIパーツの設定がなされます。

コードでCustom Viewを実装する場合

そして、Custom Viewをコードで実装する場合はClosureで初期化を行います。

final class CustomViewController: UIViewController {

    private let firstLabel: UILabel! = {
        let label: UILabel = UILabel()
        label.text = /* 省略 */
        label.font = /* 省略 */
        label.textColor = /* 省略 */
        label.textAlignment = /* 省略 */
        label.lineBreakMode = /* 省略 */
        return label
    }()

    private let secondLabel: UILabel! = {
        let label: UILabel = UILabel()
        label.text = /* 省略 */
        label.font = /* 省略 */
        label.textColor = /* 省略 */
        label.textAlignment = /* 省略 */
        label.lineBreakMode = /* 省略 */
        return label
    }()

    private let thirdLabel: UILabel! = {
        let label: UILabel = UILabel()
        (中略)
        return label
    }()

    private let fourthLabel: UILabel! = {
        let label: UILabel = UILabel()
        (中略)
        return label
    }()

    private let firstButton: UIButton! = {
        let button: UIButton = UIButton()
        button.setTitle("ボタン1", for: .normal)
        button.setTitleColor(UIColor.red, for: .normal)
        button.tintColor = /* 省略 */
        return button
    }()

    private let secondButton: UIButton! = {
        let button: UIButton = UIButton()
        button.setTitle("ボタン2", for: .normal)
        button.setTitleColor(UIColor.blue, for: .normal)
        button.tintColor = /* 省略 */
        return button
    }()

    private let thirdButton: UIButton! = {
        let button: UIButton = UIButton()
        (中略)
        return button
    }()

    private lazy var customView: UIView = { [weak self] in
        let view: UIView = UIView(frame: UIScreen.main.bounds)

        // ここでViewを追加する
        view.addSubview(self?.firstLabel)
        view.addSubview(self?.secondLabel)

        (中略)

        view.addSubview(self?.firstButton)
        view.addSubview(self?.secondButton)

        (中略)

        return view
    }()

    // MARK: - Life Cycle Methods

    override func loadView() {
        self.view = self.customView
    }

    override func viewDidLoad() {
        super.viewDidLoad()

        (ここで何もしなくていい😄)
    }

}

こちらもdidSetの場合と同じく、どのUIパーツでどの設定がされているかが分かりやすいと思います👍

letlazy varで宣言しているプロパティがありますが、その違いは「初期化時にプロパティの値を利用したいか」です。

letの場合はプロパティを利用しなくてもいい場合です。lazy varの場合は、weak selfでプロパティを弱参照にて利用することができます👍

あとがき

いかがでしょうか。はじめに載せたソースと最後に載せたソース、どちらが可読性の高いものだったでしょうか。

自分は、この方法をデファクトスタンダードにしたいと思っています(๑•̀ㅂ•́)و✧
なので、この記事を見て「良さそう!」と思った方、今すぐリファクタリングだ!!

追記

Qiitaのこちらの記事でも今回の記事と同様の内容が紹介されていました!こちらもぜひ参考にしてみてください!

qiita.com

【iOS】ちょっと待って!その画面UITableViewで作る必要ないかも

f:id:kamui_project_tony:20180125233317j:plain:w250

はじめに

ダーリンことカムイです(嘘松)

今日は個人的によく使うクラスの1つであるUITableViewについて書きます。

一覧 != UITableView

皆さんはUITableViewを普段どんな時に使っているでしょうか。

「一覧を表示する時に使うアレでしょ?」というお方、こちらをご覧ください。

f:id:kamui_project_tony:20180126205526p:plain:w200

(ほっちゃん尊い…)

こちらはInstagramのTOP画面です。大きな画像とコメント欄がセットになった項目が連続でいくつも表示されます。 実際に開発に携わったわけではありませんが、これはUITableViewで実装していると思われます。 では次にこちらをどうぞ。

f:id:kamui_project_tony:20180126205703p:plain:w200 f:id:kamui_project_tony:20180126205731p:plain:w200 f:id:kamui_project_tony:20180126205800p:plain:w200 f:id:kamui_project_tony:20180126205823p:plain:w200

途切れ途切れになっておりますが、Airbnbの詳細画面です。画面上部から動画→ホストからのメッセージ→アクセス方法→MAP→カレンダー→レビューと多くの項目が登場します。

「これも一覧!UITableViewだね!」 と思いますか?

Yesだねという方、ちょっと待って下さい。

これも実際に開発に携わったわけではないので憶測ですが、UITableViewでなくても良い画面レイアウトだと思います。

UITableViewについて、公式では以下のように述べています。

Table Viewの一般的な用途(Table Viewに最も適した用途)は、階層的なデータのナビゲーションで す。階層の最上位レベルにあるTable Viewには、最も概要的なレベルのデータカテゴリのリストが表示されます。ユーザは、ある行を選択して、階層の次のレベルに「掘り下げ」ます(ドリルダウンします)。階層の一番下には、特定の項目の詳細を表示するビュー(通常はTable View)があります(たとえば、住所録の1つのレコード)。ユーザが、この項目を編集できるようにすることもできます。

(iOS TableView プログラミング ガイドラインより抜粋)

あくまで「一般的な」という前置きの元、使い方として求めている一例として住所録を上げています。つまりタ◯ンページですね。 要はこんなレイアウトです。

f:id:kamui_project_tony:20180125233800j:plain:w250

つまり、UITableViewを利用する最大のポイントは

同じレイアウトで構成されている項目を何個も続けて表示する

ことが重要だと思います。 そのため項目が縦並びになっているからといって、UITableViewを使うと不便になるケースが度々あります。 例えば以下の3つのケースです。

1. トルツメ問題

1つだけなら良いですが、「あれとそれも消したい」と言われてしまうとUITableViewのDelegate/DataSourceメソッド内が煩雑になる一方です。 またはDataSourceを分けて考えないといけない可能性もあります。1

2. キーボード表示中に起きる問題

キーボード以外を押下した場合にキーボードを閉じたい場合、タップジェスチャーはタップイベントが存在するUITableViewでは処理が出来ず、対応策も以下の記事のように処理が面倒です。

mzgkworks.hateblo.jp

また押下したUITextField自体がキーボードに隠れてしまう問題の対応策は、UIScrollViewを継承しているUITableViewは対処法は同じであるものの、UITableViewCell内でTextFieldを使うケース自体が少ないため、ググってもその対処方が見つからないことが多いです(最近の記事であれば特に)。

「UIScrollView UITextField keyboard scroll」などと検索すると、色々情報は出てきます。 qiita.com xyk.hatenablog.com

3. 罫線問題

UITableViewCellは標準で左側に15pxの余白を用意した罫線がついておりますが、非表示処理は地味に面倒です。

特定の UITableViewCell だけ separator の線消したい

(僕はコードで処理せずにこの設定値を大きい値にして誤魔化す処理をしたりしますが、、)

f:id:kamui_project_tony:20180125235506j:plain:w200

他にもUITableViewCellで言えば選択不可の設定をしないとセル押下時にハイライト処理されてしまうなど、実際に実現したいことのためにUITableViewにある本来の機能をOFFにする対応のようなことだらけの場合は、UITabeViewで作る必要がない画面であると言えます。

今回のような項目数の多い詳細画面では

・UIScrollViewを用意2

 -> その上にUIView(ContainerView)を用意

  -> その上にUIStackView(縦並び)で用意 3

   -> その上に表示項目ごとにUIViewを用意

    -> その上に各UI部品を用意

といったやり方がベターかなと思います。

Sampleをgithubにあげましたので、よろしければご覧ください。

github.com

storyboardの設定方法として以下の記事が参考になると思います。 xyk.hatenablog.com

利点としては上記であげた問題となるケースが解消できることです。

  • 対象のViewをisHiddenにするだけでトルツメが実現できる(UIStackView)
  • タップジェスチャーイベントを拾えるため、キーボード表示中に他領域をタップすればキーボードを楽に閉じれる。
  • キーボード問題の記事もScrollViewで調べるとヒットしやすい。
  • 罫線はUIViewで実現。レイアウト調整も楽。

などが挙げられます。

逆に、UX的にUITabeViewの仕様に合わせるのが難しい処理として

  • セクションヘッダをセクションのスクロールが完了するまで画面上部に固定する
  • ハイライトさせるViewは最下部のContainerViewのみにする(UITableViewCellと同様のハイライト方法)

などは実装が大変です。 またiOSエンジニアでないと気付かない見え方であったりもするので、事前にこれらのUXを加味した画面であるかは相談した上で実装に臨んだ方が良いと思います。

最後に

どのツール系アプリにも検索画面・詳細画面は多いと思うので、「UITableViewは住所録のようなものである」と理解すると「この画面には便利・この画面には不便」の意識改革にもつながると思います。

もし実装している画面が一覧っぽくない画面なのにUITabelViewで作られている場合、息抜きがてら一度UITableViewプログラミングガイドを読み直してみてみるのも良いかもしれません。


  1. enumで整頓する方法がありますが、こちらは別途記事にする予定です。

  2. iPhone4Sの画面の高さのサイズである480pxに満たない項目量であれば、UIScrollViewは不要です↩

  3. トルツメする項目が無いのであれば不要です↩

Kotlinで可変長引数を利用する時は、同一の型の引数を一緒に使用するのを避ける

こんにちは、tobi462(過去記事一覧)です。

今日は、Kotlinで可変長引数を使用した時にハマったことがあったので、アンチパターンの共有です。

可変長引数(vararg)

可変長引数の概念はJava5からあるもので、Kotlinでも似たように配列として引数を受け取る仕様になっていますが、構文が少し違います。

Kotlinではvarargという修飾子を用いて、可変長引数を宣言します。

// 宣言
fun printAll(vararg values: String) {
    println(values::class.java) // => class [Ljava.lang.String;
    values.forEach {
        println(it)
    }
}

// 使用
printAll("Apple", "Orange", "Banana")

配列自体を引数として渡す場合には、先頭に*をつけます。

val fruits = arrayOf("Apple", "Orange", "Banana")
printAll(*fruits) // *を頭につけないとコンパイルエラー

同一の型の引数を一緒に使う

さて、ここでクイズです。

以下のコードは先程の関数にデフォルト引数を追加したものですが、何が出力されるでしょうか?

fun printAll(vararg values: String, postfix: String = ".") {
    values.forEach {
        println(it + postfix)
    }
}

val fruits = arrayOf("Apple", "Orange", "Banana")
printAll(*fruits, "!") // クイズ:何が出力される?

おそらくここまでの流れから答えの察しはつくのではないかと思います。

以下のように出力されます。

Apple.
Orange.
Banana.
!.

つまり以下のコードは一見すると、可変長引数としてfruitsを渡し、第二引数として”!”postfixとして渡しているように見えますが、実際にはどちらも可変長引数として扱われているのが分かります。

printAll(*fruits, "!") // どちらも可変長引数として渡される

ちなみに第二引数にデフォルト値が使用されていない場合は、名前付き引数で明示しないとコンパイルエラーになるため、この問題は起こりません。

printAll(*fruits, postfix = "!") // `postfix = `がなければコンパイルエラー

教訓としてのアンチパターン

今回得られた教訓としては、可変長引数を利用する時は、同一の型の引数を一緒に使用するのを避けるべきであるということです。

デフォルト引数を利用しなければ問題ないと感じるかもしれませんが、それでも引数の境界が分かりづらくなり、APIとしては利用者に誤解を与えやすいため避けるべきかと思います。

最後に

といった形で、今日はKotlinのアンチパターンについての記事でした。

Kotlinはよくデザインされている言語だと思いますが、こういったハマリポイントもあるのだと分かったのは良い収穫だったと思います。

ではまた次回。

ターミナルからXcodeを1コマンドで開くCLIツールを作ってみた

どうも、攻殻機動隊シリーズでは S.A.C が一番好きな tobi462 です。

今回は Swift Package Manager で、ターミナルからXcodeを開くCLIツール xcode-open を作ってみたので、その紹介です。

f:id:yu_dotnet2004:20180121012156g:plain

モチベーション

  • ターミナルからXcodeを1コマンドで開ける
  • バージョン(e.g. 9.1)も指定できる
  • 開くバージョンを記憶することもできる

使い方

インストール

Homebrewでインストールできます。(ビルドのためにXcode 9.0以上が必要です)

$ brew install YusukeHosonuma/xcode-open/xcode-open

あるいはGitHubのリリースページからバイナリを直接DLしてもOKです。

使い方

Xcodeプロジェクトまたはワークスペースのあるディレクトリに移動し、以下のようなコマンドが使えます。

デフォルト(xcode-selectで指定されているXcode)で開く:

$ xcode-open

Xcodeのバージョンを指定して開く:

$ xcode-open 9.1

バージョン指定を記憶する:

$ xcode-open 9.1 --save
$ xcode-open # 9.1 で開かれるようになる

オススメのエイリアス設定

xcode-openだと微妙に長いので、.bash_profile.zshrcに以下を追記することを推奨します。

# xcode-open
alias xopen="xcode-open"

xopenで開けるようになります

$ xopen 9.1

なぜ作ったか?

ターミナルから起動したい

わたしは普段、ターミナルが作業の主軸になっています。

ターミナルからキーワード絞込で Git の作業ディレクトリに移動し、そこから VS Code を立ち上げたり、IntelliJ IDEA などを起動したりしています。

$ code . # VS Code を起動
$ idea . # IntelliJ IDEA を起動

Xcodeでも同様に1コマンドで起動したいと思ったのが最初のモチベーションです。

バージョンを指定したい、記憶させたい

複数のプロジェクトを同時に担当していると、そのプロジェクトによって利用している Xcode のバージョンが異なるということはままあります。

あるプロジェクトでは最新の 9.2 だけど、もう1つのプロジェクトでは 8.3 といったケースです。

であれば、プロジェクト毎に利用するXcodeバージョンを固定してしまいたいというのが2つ目のモチベーションです。

Swift Package Manager で CLI を作ってみたい

Swift Package Manager は現在バージョンは4まで出ています。

そろそろ実践で使えるレベルかなと思い、せっかくなのでCLIを作ってみようと思い立ちました。

作ってみた感想

わたしが欲しいものを作ったので当たり前ではありますが、必須ツールになりました(自画自賛?)。

もう手がxopenを覚えてしまったので、他のMacで入って無いときあれ?となってりします。

Swift Package Manager はそれなりに良く出来ていると感じたのですが、面倒な部分やハマリポイントがあったり、Xcodeが正しくファイルやシンボルを認識しなくなることがあり、ちょっと面倒なところも感じました。これくらいの小規模なツールであれば Ruby とかで書いたほうが早いかな、という気もしました。

Swift Package Manager は標準でXcodeプロジェクト生成機能を備えているので、Xcodeとの相性については今後改善を期待したいと強く思いました。

最後に

といった感じで、Swift Package Manager で CLI ツールを作ってみたお話でした。

内部的な作りなど技術的な面については、またどこか別のタイミングで記事にできればと思っています。

もちろんOSSとして公開 していますので、IssueやPRも歓迎です。

NavigationBarを含んだ画面でViewで全体を覆う方法

qiita.com

通信中に画面を操作させたくない理由から、マスキングを画面にすることがよくあると思います。 ただNavigationBarを含む場合、単純にself.viewにaddSubviewするだけではNavigationBarまで覆ってくれません。

f:id:kamui_project_tony:20180119010423p:plain:w200

その対処法としてappDelegate.windowに対しaddSubviewをするという手段もあるのですが、windowに直接Viewを貼り付けするのはバグの温床になるので避けた方が良いです。

記事ではself.navigationController?.viewに対しaddSubviewをすることで、対象画面でのみ処理を完結することに加え、storyboard上に部品を配置する方法で実現しています。普段の開発でstoryboardにView部品を設置している人にとっては馴染みやすい方法なのではと思い、やってみました。

f:id:kamui_project_tony:20180119010452p:plain:w200

追伸: 今日参加したAKIBA.swiftのAKIBA枠のアニメの話面白かったです(´・ω・`)

ストレスを溜めやすいエンジニアの体調管理

今週のお題「体調管理」

どうも、NEWGAME!では八神コウちゃんが一番好きなペンギン村の住人@tobi462です。

今回は今週のお題である体調管理について書いてみたいと思います。

事前に書いておきますが、技術的な要素は一切ありません。(Tech Blogなのにね・・・)

体調を大いに崩した去年末

毎日のような頭痛、朝起きるのはものすごく辛い、仕事もプライベートも楽しくない、なんだかずっと疲れている。

というのが私の去年のピークでした。はい、有り体に言って軽いうつ状態というやつですね。

仕事は続けられていたものの大した成果も出せず、わりと辛い日々を過ごしていました。

しかし、社内カウンセリングに行ったきっかけから、少しずつ回復しつつある現状の体調管理について書いてみたいと思います。

IT業界はストレスを溜めやすいですし、同じように苦しんでいる方に私の経験を共有して、何かしらのプラスにつながれば良いと思い記事にしてみました。

寝る前に思考内容を書き出す

前述したようにわりと限界が近い状態で、社内のカウセリングに行きました。(カウンセリングを勧めてくれた同僚にはとても感謝してます)

その時の私は、いつも仕事のことが頭から離れないというのがストレスというか悩みでした。

その時カウンセラーにオススメされたのが、金曜日の夜などに思考内容を書き出して整理するというものでした。

そのメソッド自体は知っていたものの、その時の私にそれを実践しようという発想はもちろん出てくるものではありませんでした。

それから不定期ではあるものの寝る前に気持ちのダンプっぽいものをやるようになりました。

手書きの方が良いのかもしれませんが、とりあえずテキストエディタ(これは余談ですがVSCodeを好んで使っています)を立ち上げ、思うがままに考えをタイプして、最後は保存せずに破棄するというやり方をしてみました。

そうしたところ、やはり休みの日でも仕事のことを考えてしまうことはあるものの、少しだけ回数が減ったように感じ、前よりはストレスレベルも落ちたような気がしました。

運動駆動開発

次に運動の回数を増やしました。

私は以前からスポーツジムに通って、軽いランニングはしていたのですが、その頃は運動の回数もめっきり減ってしまっていました。

そこで、最初は3日に1回を目標にし、次に2日に1回のペースで、30分ほどのランニングをするようになりました。

これの効果は抜群で、久しぶりに自分の思考回路が戻ったというか、頭の中に渦巻いている悪いものが抜けた気がしました。

運動駆動と表現したのは、忙しくなるとあっという間に運動しなくなるという事実からです。

しかし、運動しないことが最大のリスクという認識があったので、運動から駆動させるという考えを採用しました。

調子にのらない

さて人間不思議なもので、体調が良くなってくると妙に強気になってきます。

全快にはほど遠い状況でありながら、これはもう大丈夫だろうと夜遅くまでコードを書いたり、あるいはアニメを見たりすることもありました。

結果はもちろん悲惨なもので、翌日の朝は体調が悪く、仕事もあまり調子が乗らないこともしばしばです。

体調が少し良くなったからといって油断しないこと、これは今でもハマりやすいポイントなので気をつけるようにしています。

オープンソースであれ

前述したように、去年、社内のカウセリングに行きました。

あまりそういったことは誰かに話そうとも思わなかったのですが、あえて部署内の日報に書いてみました。

その後も体調悪いとか、今日は運動するとか、回復傾向かもとか書くようにしました。

それの効果かは分かりませんが、体調が悪くて生産性が悪い自分を少し許せるようになったような気がします。

体調が悪ければ、それを隠さずにオープンにしてしまう。そうすると少し客観的に慣れて、自分自身でも無理をしなくなるような気がしました。

がんばらない勇気

というのが大切だと改めて思いました。

とくに脳が不調の時は、がんばろうと考えたって、ぶっちゃけわりとどうしようもない気がします。

であれば、勇気をもってがんばらないほうが最終的には生産的であるような気もします。

まとめ

といった形で、私の最近の体調管理、あるいは体調を回復するためにしていることを記事にしてみました。(これちゃんとお題のテーマに沿ってますかね・・・?)

今回の記事で挙げたのは以下です。

  1. 寝る前に思考内容をダンプする
  2. 運動駆動開発
  3. 少し体調が良くなっても過信しない
  4. オープンソースに振る舞う
  5. 頑張らない勇気

何かしら参考になるものがあれば幸いです。