Swift de 脳トレアプリ 第1章

Blog Single

ボケが始まったのか、最近忘れ物・忘れ事が増えてきました←
本格的に脳トレをしないとと思い始めている日々でございます…
そんなわけで!(どんなわけで)
今回はSwift de 脳トレアプリシリーズでございます!

第1章 目次

  • 前置き
  • タイトル画面を作っていこう

前置き

前回の序章を投稿した後にXcodeのアップデートをしたので、改めて以下の開発環境で進めていきます。

  • MacOS High Sierra 10.13.6
  • Swift 4.2.1
  • Xcode 10.1

バージョンは随時更新していきますので、以降は本シリーズのGitHubのREADMEをご確認ください!

また、あくまでSwift4の書き方が伝わったらいいなーと思いこのシリーズを進めていきますので、
なるべくXcodeのStoryboardはなるべく使わない方向でいきます。
デザインや機能がダサくなるかもしれませんが、そこはお手柔らかに見ていただけると幸いです。
え、決してハードルを下げてるわけでは←

さて前置きが長くなりましたが、早速第1章の本編といきましょう!

タイトル画面を作っていこう

さて、アプリといえばタイトル画面。
アプリの顔とも言えますから、初回にはもってこいですね。
というわけで作っていきましょう。
以下は画面の完成イメージ図です。

あぁデザインがひどすぎる←
この素人デザインは決して真似しないでください←
学ぶならこちらの記事を参考にしてください!!(切実に
0から1を生む為に〜design編〜|タイトルロゴの作成

…気を取り直して。
このイメージ通りにViewControllerから要素を配置していきます。

// class TitleViewController

override func viewDidLoad()
{
    super.viewDidLoad()

    // write here
}

UIViewControllerにあるライフサイクルメソッドのviewDidLoad()内に描画する処理を書いていきます。
ちなみにこのライフサイクルは、メソッド名の通りviewのloadが終わった後に実行されます。

文字を配置

では早速、タイトルと画面下の文字を配置しましょう。
前回の序章では使いませんでしたが、正確なレイアウトをするためにAutoLayoutを使用していきます。
まずそれぞれのUILabelの基本情報をプロパティに定義しておきます。

// class TitleViewController

let titleLabel: UILabel = {
    let label = UILabel()
    label.text = "Brain Training"
    label.textAlignment = .center
    label.textColor = UIColor.black
    label.font = UIFont.systemFont(ofSize: 24.0, weight: .bold)
    label.translatesAutoresizingMaskIntoConstraints = false
    return label
}()

let navigateLabel: UILabel = {
    let label = UILabel()
    label.text = "TAP TO START"
    label.textAlignment = .center
    label.font = UIFont.systemFont(ofSize: 20.0, weight: .bold)
    // ColorAsset (iOS 11.0 以上対応)
    label.textColor = UIColor(named: "textGray")
    label.translatesAutoresizingMaskIntoConstraints = false
    return label
}()

テキストとフォントサイズ、色の設定ですね。
navigateLabelだけ色の指定にiOS11.0から対応になったColorAssetを使用してみました。
設定方法はXcodeからでも、jsonファイルでも可能です!
XcodeからのColorAssetの設定方法は以下を参考にしてください。
[iOS 11][Xcode 9][新機能] Asset Catalogで色を定義できるようになりました | DevelopersIO
translatesAutoresizingMaskIntoConstraintsはAutoLayoutを使う上で必ずfalseにする必要があるので忘れずに!←よく忘れる人が言ってます

次にAutoLayoutの設定とviewにUILabelを追加していきます。

// class TitleViewController

override func viewDidLoad()
{
    // 省略

    // タイトル
    view.addSubview(titleLabel)

    var xConstraint = NSLayoutConstraint(item: titleLabel, attribute: .centerX, relatedBy: .equal,
                                         toItem: view, attribute: .centerX, multiplier: 1,
                                         constant: 0)
    var yConstraint = NSLayoutConstraint(item: titleLabel, attribute: .centerY, relatedBy: .equal,
                                         toItem: view, attribute: .centerY, multiplier: 1,
                                         constant: 0)

    NSLayoutConstraint.activate([xConstraint, yConstraint])

    // 画面下の文字
    view.addSubview(navigateLabel)

    xConstraint = NSLayoutConstraint(item: navigateLabel, attribute: .centerX, relatedBy: .equal,
                                     toItem: view, attribute: .centerX, multiplier: 1, constant: 0)
    yConstraint = NSLayoutConstraint(item: navigateLabel, attribute: .bottom, relatedBy: .equal,
                                     toItem: view, attribute: .bottom, multiplier: 1, constant: -40)
    NSLayoutConstraint.activate([xConstraint, yConstraint])
}

viewにaddSubviewで要素追加、AutoLayoutにはNSLayoutConstraintを使います。
タイトルは画面中央に配置するため、viewのcenterXcenterYと一致するように指定しています。
画面下の文字は同じようにviewのcenterXと、viewの下bottomから40離れた位置に来るように指定しました。
これで文字の配置は完了です!

画像を配置

続いてタイトル文字の後ろの画像を配置します。
使用する画像は予めproject名/Assets.xcassetsに追加しているものとします。
Xcodeからの追加方法は以下を参考にしてください。
Xcodeの概要:画像を追加する
こちらもUILabelと同じように基本情報をプロパティに定義します。

// class TitleViewController

let logoBrainImageView: UIImageView = {
    let imageView = UIImageView(image: UIImage(named: "logo_brain"))
    imageView.translatesAutoresizingMaskIntoConstraints = false
    return imageView
}()

UIImageViewにどの画像を使うか指定しているだけですね。単純!
こちらにもAutoLayoutを入れましょう。

// class TitleViewController

override func viewDidLoad()
{
    // 省略

    let widthConstraint = NSLayoutConstraint(item: logoBrainImageView, attribute: .width,
                                             relatedBy: .equal, toItem: nil,
                                             attribute: .notAnAttribute, multiplier: 1.0,
                                             constant: 210)
    let heightConstraint = NSLayoutConstraint(item: logoBrainImageView, attribute: .height,
                                             relatedBy: .equal, toItem: nil,
                                             attribute: .notAnAttribute, multiplier: 1.0,
                                             constant: 188.5)
    let xConstraint = NSLayoutConstraint(item: logoBrainImageView, attribute: .centerX,
                                         relatedBy: .equal, toItem: view, attribute: .centerX,
                                         multiplier: 1, constant: 0)
    let yConstraint = NSLayoutConstraint(item: logoBrainImageView, attribute: .centerY,
                                         relatedBy: .equal, toItem: view, attribute: .centerY,
                                         multiplier: 1, constant: 0)

    NSLayoutConstraint.activate([widthConstraint, heightConstraint, xConstraint, yConstraint])
}

配置自体は画面中央ということでcenterXとcenterYの指定はタイトルの文字と同じですが、画像はサイズの指定も必要です。
今回はデザインに合わせるということで、widthheightに直接指定しました。
これで画像の配置も完了です!

背景グラデーションを配置

次は背景の放射状のグラデーション。
画像を用いてもいいのですが、CALayerでも再現できます。
CALayerはUIViewではできない細かい描画で色々と便利です。
ちょっとここの処理は初心者の方には難しく感じてしまうかもしれませんが、ざっくり噛み砕いて説明していきます。
描画するdrawメソッドをoverrideしたいので、CALayerを継承した専用のクラスを作りました。
コード全文はこちらから
ありがたいことに、描画用クラスCGContextdrawRadialGradient()という放射グラデーション作成専用のメソッドが用意されていますのでそちらを使いましょう。
以下は描画処理の抜粋です。

// class GradientLayer

override func draw(in ctx: CGContext)
{
    ctx.saveGState()

    let colorSpace = CGColorSpaceCreateDeviceRGB()
    let locations: [CGFloat] = [0.0, 1.0]
    let gradient = CGGradient(colorsSpace: colorSpace, colors: colors as CFArray,
                              locations: locations)
    let center = CGPoint(x: bounds.width / 2.0, y: bounds.height / 2.0)
    let radius = min(bounds.width + 100, bounds.height + 100)
    ctx.drawRadialGradient(gradient!, startCenter: center, startRadius: 0.0, endCenter: center,
                           endRadius: radius, options: CGGradientDrawingOptions(rawValue: 0))

    ctx.restoreGState()
}

先ほどのメソッドに描きたいグラデーションのパラメータを指定してやるだけです。
今回は2色しか使用しないのでcolorsには白とグレー。
各色のグラデーションの位置locationsには0と1、つまりグラデーションの開始と終了位置だけ指定しました。
あとは円形がはみ出すようにしたいので、radiusに幅と高さにそれぞれ100上乗せしています。

画面のタッチイベント

タイトル画面といえば、画面をタップした後に次の画面に進む。
というのが一般的ですね。
次の画面はまだありませんが、どこに処理を書くかだけ用意してしまいましょう。
と言っても非常に単純です。
UIViewControllerにタッチ終了時に実行されるtouchesEndedメソッドがあるので、そこに次の画面への遷移処理を書くだけです。

// class TitleViewController

override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?)
{
    // ここに次の画面への遷移処理を書く
    print("route to next")
}

完成

これで置きたい要素はすべてですね。
シュミレータで動かしてみましょう!
ちょっと遊びで文字に影、画像にアニメーションをつけてみました!

というわけで続きは次章へ!!

おまけ

Xcodeでアプリのアイコン設定をしました。といってもAssetsに設定するだけなので簡単です。
設定方法は以下を参考にしてください。
(初心者向け)Swift3.0で初アプリ – アイコンを登録してみる – Qiita
ちなみにシュミレータのHOMEだとこのような感じに表示されます。

最後に

今回はまだ内容量の少ないタイトル画面の作成でしたので、割とシンプルだったでしょうか。
要素を配置するだけなら割とコード量も少なく、思ったままに書けますね。
時間の都合とテクニックの問題から、説明を割愛してしまった箇所がありますが、
そちらはまた別の章あるいは番外編でご紹介できたらなと思います!

いやーそれにしても素人デザインを公開してしまったことが精神的ダメージです←
(くれぐれもデザインだけは参考になさらぬように)

次章予告

  • トップ画面を作っていこう (1)

参考

Swift de 脳トレアプリシリーズはGitHubに公開しています!

Posted by Mao Miyaji
千葉にある夢の国を愛して止まない、元「魚のお姉さん」のエンジニア。PHP, TypeScriptメインで、暇さえあれば色々な言語を一かじり。

Other Posts: