your3i’s blog

iOSエンジニア。頑張る⚉

iOSDC 前夜祭 メモ

聞いたspeech

f:id:your3i:20180830232401p:plainf:id:your3i:20180830232545p:plain

感想

  • ドア→同じUI同じ振る舞いするから、慣れてるユーザーなら本能的に使える?
  • MDM全然わからない
  • UIテストを利用してスクショとるGET
  • Design system いい、はやくやりたい
    • storyboardでのスタイル設定使わない、そこまで徹底的にやるのはまだ考えてなかった
    • Sketchファイルをgitで管理するのが、diffもわかるし、もしかして意外といい?
  • ダンボーさんの話よかった

Swiftでの日付フォーマットのメモ

アプリでDateの扱いは2パターンある

  • APIの日付文字列をDate型にマッピング
  • Date型をユーザーに見せるStringに変換

この2つのタイミングで、DateFormatterが使われる。DateFormatterにはdateFormat, calendarやlocaleを設定必要があり、どんなタイミングでどんな値を設定すれば?って感じに混乱になる。

各ものの概念の理解

calendar -> 年月日などの要素の数え方と関係

dateFormat -> 日付情報はどういう風に並んでるのか

locale -> 地域のこと。これで並び方や表記が変わる。localizationが行われる

timezone -> timezoneを設定することで、時間がそのtimezoneの時間に変換される

 

ここで、私は最初localeがtimezoneの機能持っていると間違えしてた。

localeはあくまでも表示に使われる。

 

↓すごいいい感じのまとめです。ありがとうございます!

【Swift】Calendar, Locale, TimeZoneなど、国際化周りのクラスを整理 - しめ鯖日記


String -> Date

 フォーマット固定の日付文字列を正しい日付のDate型に変換。

let formatter = DateFormatter()
// dateFormatをAPIのフォーマットに合わせて設定(rfc3339)
formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSZZZZZ"
// localeをen_US_POSIXに設定
formatter.locale = Locale(identifier: "en_US_POSIX")
// calendarを日付文字列だ使ってるcalendarに設定
formatter.calendar = Calendar(identifier: .gregorian)
let date = formatter.date(from: "2018-06-16 19:27:30...")

en_US_POSIXとは↓、よくわからないけど…

a locale that's specifically designed to yield US English results regardless of both user and system preferences

rfc3339とはネット上でよく使われてるフォーマットのポリシーみたいなものかな、iso8601もあるけど、どっちがどっちかも正直わからない…rfc3339はiso8601からのものだけわかった

timezoneは文字列に含まれてるため、設定しなくてもいいかな。

Date型をユーザーに見せるStringに変換

Stringに変換するとき、変わるかもって考える必要があるのは2つ

  • 表記
  • 表示される時間

[表記]
表記はdateFormatを設定することで、固定なフォーマットで表示することができる。しかし、地域によって慣れてる表記が違うため、固定しちゃうとよくない。例えば、地域Aの表記は月/日/年で、地域Bの方が日/月/年の見方が多い場合、固定したフォーマットで月/日/年で表示した場合(06/07/2018)間違えた日付が地域Bのユーザーに取られる。そのため、ちゃんとlocalizationをやる必要がある。

[表示される時間]
timezoneは特に指定する必要がないと思う。ユーザーのtimezoneが使われるはず。

Date -> String

// styleを使う
// Localeはcurrentが使われるだしょう
let formatter = DateFormatter()
formatter.dateStyle = .short
formatter.timeStyle = .short
let localizedString = formatter.string(from: Date())
// templateを使う
// 必要な要素を設定すれば、localizationが勝手にやってくれる
let formatter = DateFormatter()
formatter.dateFormat = DateFormatter.dateFormat(fromTemplate: "yyyyMM", options: 0, locale: nil)
return formatter.string(from: self)

templateのcheatsheet

f:id:your3i:20180616200417p:plain

記録・「悲しみよこんにちは」

悲しみよこんにちは - Wikipedia

 

二日間で一気に読み終わった。

主人公が自由な個性があって、自由が縛られるときの矛盾な感情と反抗が自然に出てきて、そして自分らしく生き方をまた取り戻した。その中、アンヌというもともと彼女の義理の母になる女性、そして彼女のわがままな生き方の邪魔になる人、がなんとなく彼女の所為で死んだ。夏休みの別荘でのことだった。主人公はこのあとは自分らしく、無責任でわがまま自分勝手で生きているけど、ずっと夏休みの別荘でアンヌのことを思い出す。

その時の気持ち、感情は主人公は「Tristesse」で定義した。映画だと「Sadness」 、日本語だと「悲しみ」。

生き方や考え方違う人が一緒にいると、色々起きるんだね。

主人公はよくアンヌが冷たいとか、人をよく軽蔑してるようなとかって思ったらしい。でも実際どうかな。そして、アンヌが冷たいなら、主人公が残酷。自分のために人の愛や希望を壊すほど手段選ばず。でも考えてみたら、愛情とかは主人公にとって一瞬なものらしい、しかアンヌにとっては違うかも。愛情に対する態度は人それぞれ。主人公は人生をいっぱい楽しむタイプ、しかも若いから、人の苦しみなどはあんまり理解しようとしないだろう。だからそういう悲劇が起きた。

主人公によっては、アンヌとは色々あったけど、その考え方の衝突で、いろんな感情が生まれたから、強い影響か印象があったはず。アンヌの死に、後悔などの気持ちより悲しむんだ。もしかして、家族のメンバーの死に悲しむみたいに。

 主人公の考え方は彼女の個性通りさっぱりで、陽気で、たまに大人から見ると幼稚だけと魅力的。おかげで、さらさら読めた。

この本はkindleの中にずっとあった。なんでもっと早く読まなかっただろう、って思いほど面白い。一回読んだら、二回目はまたいろんな収穫が出てきそうな気がする。次回英語で読みたい。

Heroを使ったmodal viewcontrollerをドラッグ閉じるの実装

この間、画像のプレビュー画面を作った。

よくある、フルスクリーンのズームイン・ズームアウトできる画面。

閉じるときは、一応×ボタンで閉じれる。

でも、やっぱりTwitterみたいに、下スワイプして閉じれる方がかっこいいだね。

 

Heroとは

HeroはTransitionをいい感じにするライブラリー。
GitHub - lkzhao/Hero: Elegant transition library for iOS & tvOS


実装

例えば、ImageViewerViewControllerがある。

 f:id:your3i:20180430135652p:plain:w300


Screen Bをドラッグして、背景は透明になっていく、最後Screen Aになるというinteractiveなtransitionを作る。
f:id:your3i:20180430140019p:plain:w260


Screen BのUI構造としては:
f:id:your3i:20180430141438p:plain:w300


そして、

Step #1 Heroをimport
import Hero
Step #2
override func viewDidLoad() {
        super.viewDidLoad()
        hero.isEnabled = true // このviewControllerがhero使えるようにする

        // transition中透明になっていくので、animation typeをfadeに設定
        hero.modalAnimationType = .fade
}
Step #3 PanGestureRecognizerをviewにつける
override func viewDidLoad() {
        super.viewDidLoad()
        hero.isEnabled = true
        setupPanGesture()
}

private func setupPanGesture() {
        let panGesture = UIPanGestureRecognizer(target: self, action: #selector(handlePanGesture(_:)))
        view.addGestureRecognizer(panGesture)
}
Step #4 Gestureをハンドリング
@objc func handlePanGesture(_ gestureRecognizer: UIPanGestureRecognizer) {
        let translation = panGesture.translation(in: nil)
        
        // transitionがどこまで進んだのかのprogessを計算。
        // 上スワイプしても閉じれるようにしたいから、absを使う
        let progress = abs(translation.y / 2) / view.bounds.height

        switch panGesture.state {
        case .began:
                // transition を始める。
                hero.dismissViewController()
        case .changed:
                // transitionのprogressを更新
                Hero.shared.update(progress)

                // このままじゃ、ただだんだんfadeして画面が閉じられるだけ
                // 下にdragすると、画像も下にいくべき
                // scrollviewのpositionを変える。
                let currentPosition = CGPoint(x: scrollView.center.x, y: translation.y + scrollView.center.y)
                Hero.shared.apply(modifiers: [.position(currentPosition)], to: scrollView)
        default:
                let velocity = panGesture.velocity(in: nil).y

                // dismiss完成の条件のチェック
                if progress + abs(velocity) / view.bounds.height > 0.5 {
                      // transition animationがfadeだから、scrollviewのtarget positionが元々のposition
                      // transitionが自動的に完了する間も、scrollview(要するに画像)が続いて下に移動して消えていくを実現するために、
                      // scrollviewのtarget position を変える必要がある
                      // そのため、下ドラッグと上ドラッグそれぞれの場合のpositionを計算。
                      let destinationY: CGFloat = {
                      if velocity >= 0 {
                          return view.bounds.height + scrollView.bounds.size.height / 2
                      } else {
                          return -scrollView.bounds.size.height / 2
                      }
                  }()
                      
                  // そして、「scrollviewの最終状態が変わったよ」ってHeroに伝える
                  // Heroがtargetのpositionによって、続きのtransitionの間で、
                  // scrollviewをいい感じにanimateしてくれる
                  let destinationPos = CGPoint(x: scrollView.center.x, y: destinationY)
                  Hero.shared.changeTarget(modifiers: [.position(destinationPos)], to: scrollView)
                  Hero.shared.finish(animate: true)
              } else {
                  // dismissの完成条件が足りなかったら、transitionをキャンセル
                  Hero.shared.cancel()
              }
          }
    }

韓国語の自学日記 〜4〜

続いている!

でも頻度はかなり少なくなった。一週間一回ネットのレッスンを見る。

一回多くても30分だけだけどなぁー

 

最近の発見は、今ままで真似して喋った挨拶の言葉、anihaseyo~

発音が間違ってるんだ!

正しいのは、An-nyong Ha-se-yo。

まだ韓国語打てない><

 

そして、ドラマを見ようと思ってる。

知り合いのおすすめで、「恋のスケッチ~応答せよ1988」を見る。

noneを含めたOptionalのenumを比較するとき要注意

例:

enum Foo: String {
    case hoge
    case none
}

var a: Foo = .none
print(a == .none) // true

var b: Foo? = Foo.none
print(b == .none) // false

こうなる理由は:
f:id:your3i:20180403231233p:plain

後者の`.none`がOptionalタイプの.noneになっている。
要するにnilだ。

print(nil == .none) // true

こういうのを防ぐために、ちゃんとunwrapするか、または.noneの前ちゃんとenum名前をつける。

var b: Foo? = Foo.none
print(b == Foo.none) // true
print(b! == .none) // true

韓国語の自学日記 〜3〜

まだ続いてるよー

子音が14個だけだと思ったけど、また新しいの出てきた。

発音近いのもいくつがあって、困る。

でも韓国人もちゃんと聞いてそれぞれわかるのできない人も多いらしい。

そして、発音近いのがあんまり使われないから、大丈夫そう。

前学んだのはだいぶ忘れてる。

ちゃんと覚えていく必要があるんだね。

今日あと少しの時間で、今週の分の終わらせよう。