20190804のSwiftに関する記事は13件です。

未経験が現場iOSアプリ開発で出会うRuby関連

どうもみなさんこんにちはiOS青二才です。

未経験でiOS開発現場に入ると、思いの外今まで頑張って技術をあげてきたSwift以外にも様々なものに出会い自分の知見・技術レンジが狭いことに気付きます。
現場に入る前も幅広く開発をしてる人は出会うことがあったり使用したりの経験があると思いますが、私はそんなことはなかったので、「あれ、rubyさん介入してるやんなんやこれ」ってなった物のメモをおきます。

Ruby

  • 日本産のスクリプト言語
  • Webサイト制作に使用
  • Webアプリ制作に使用
  • スマホアプリ開発に使用
  • サーバーサイド開発に使用

まずiOSアプリ開発とRubyとの関係性と知る

色々と掘り下げる前に、iOSアプリ開発に置いてのRubyとの関係性を知るべきだと思いました。
Rubyという言語の一部の特徴は上にあげたものたちですが、サーバーサイド側の処理を担うことが多々あります。
ここでまず一つ、「開発に置いてサーバー連携はrubyで、、、」という繋がり1が生まれます。
そしてMacにはデフォルトでRubyが導入されているのですが、サーバー側処理とか個人開発で使わなきゃ出会いないよね、と思っていたのですがところがどっこい。実は触れてました。

以下で説明するgemですが、実はCocoapodsをインストールする際に使用していたんですね。最近は既存のプロジェクトを改修したりすることばかりで、新規でインストールする機会を逃していたので完全に忘れていました。
よくよく調べてみるとCocoapodsRubyで実装されているらしく、インストールはRuby経由ということになります。

ライブラリ / パッケージ

プロジェクトのreadmeをみるとgem installとかbundle install とかそういうのを見かけることがあります。
調べてみると、gemとはRubyGemsが公開しているRubyのパッケージ管理ツール。とかいろんなところで説明されています。正直、意味がすんなり入ってこなかったので調べました。

まずここで一つはっきりさせておきたいのが
ライブラリ = パッケージ
ということです。
初歩的なことかとは思いますが、新しいものを理解するときに事前に脳内にある知識と説明を照らし合わせたときに少しでも差異があると混乱を招きますので、この際にはっきりさせました。

iOS開発で使用するCocoapods Carthageライブラリ管理ツールと認識していたので、パッケージ管理?また違うもの?っとなってしまったのでここで認識を共通化させたかった次第です。
以下の説明では、同等のものと認識してください。
なおここではライブラリという言葉で統一化します。

Gem

  • RubyGemsが提供するRuby用のライブラリ管理ツール
  • Rubyにおいてgemとはライブラリのこと(太郎さん、花子さんを人間と呼ぶ感じ)
  • ターミナルにてgemコマンドで使用
  • gemコマンドにてビルド、アップロード、ダウンロード、インストールを行う
  • gemfile gemfile.lockというのが作成されるので、gemfile内にライブラリを記載して使用

まずRubyGemsですが、これはそもそものgemを使用できる環境、または管理するシステムです。これを
gemを使用するにはまずそれ自体のインストールから始めます。丁寧なreadmeの場合はだいたいプロジェクトをビルドするまでのフローが書いてあると思いますので、それに従ってください。

こいつがSwiftでいうCocoapodsなどの管理ツールに該当するのではと思います。

参考
https://ja.wikipedia.org/wiki/RubyGems
https://qiita.com/sumyapp/items/5ec58bf3567e557c24d7

bundler

gemの依存関係とバージョンを管理するためのツール。との説明が多いですがこの表現も紐解くと

  • bundlergemの一つ
  • 複数のgemの使用の間で生まれるgemA version1 と gemB version1が上手く動作しない、というのを管理して解消してくれる。
  • gemfile gemfile.lockに準拠して管理
  • gemの管理をする gem、それがbundler

ということになります。bundlerを入れましたら、基本的にはbundle installなどbundlerを介してgem達のインストールを行うのが良いとされているようです。

参考
https://qiita.com/oshou/items/6283c2315dc7dd244aef
https://techacademy.jp/magazine/19840

rbenv

rbenvは、複数のRubyのバージョンを管理してくれて、かつプロジェクトごとのRubyのバージョンを指定して使うことができるツールです。Rubygemsがライブラリ管理ツールだとしたら、こちらはRuby管理ツールとでもいうんですかね。Homebrewなどでインストールして使います。

rbenvRubyのバージョン操作等を行うことがあるかどうかわわかりませんが、だいたいgem bundlerとセットで使用されていることが多いはずです。
ちなみアールベンブとかアールビーエンブという呼び方をするそうです。

まとめ

登場したのは以下のもの達

  • rbenv - Rubyのバージョンを管理
  • gem(RubyGems) - Rubyのライブラリ管理ツール
  • bundler - gem(ライブラリ)の一つで、gemの相互性などの管理をする

iOSアプリ開発でRubyを扱っていた場合ほぼほぼこれらを導入しているケースが多いのかなと思います。
Rubyも完璧にする!というのもまた先が長い話になるので、チーム開発規模のプロジェクトに介入する際は最低限入れておくべき部分かなと感じました。

という訳で、知見を広げるいい経験でした。ビルドの際、何かライブラリ系のエラーにエンカウントした場合はこの辺にも何か原因があるのでは、という発想をもてるようにもなったかと思うのでとてもいい機会だったと思います。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

UILabel メモ

やり方忘れるのでメモ

Autoshrink をminimum Font Scaleにすると 全部表示される。

スクリーンショット 2019-08-04 18.08.04.png

スクリーンショット 2019-08-04 18.11.05.png

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

FirebaseのRealtimeDatabaseでobserveSingleEvent取得結果で0件だったときの判定方法

FirebaseのRealtimeDatabaseでobserveSingleEvent取得結果で0件だったときの判定方法

はじめに

FirebaseのRealtimeDatabaseから新規追加されたデータを逐次取得する でデータ取得でデータベースに未登録のことをどう判定すればよいのかわからないことがありました。
単純だったのですが、気づかなかったので共有します。

データの一括取得でデータが存在しないってどう判定すればいいの?

Firebase公式ドキュメントの データの1回読み取り を見ると一見簡単に一括取得できそうです。
しかしながら、データがないときはどうればいいのでしょうか?

value イベントのリッスンという項目に答えがありました。

データが存在しない場合、スナップショットから返されるのは、exists() を呼び出した場合は false で、value プロパティを読み取った場合は nil です。

データが存在しない場合は、nilとなるでした。

前回のインデックス用のデータ取得のサンプルコードを例にすると guard let 〜 else { return } のreturnルートに入るとデータが存在しないことになります。
サンプルコードを少し修正して print 文を加えてみました。

fireDatabase.child("index").observeSingleEvent(of: .value, with: { (snapshot) in
    // Get user value
    guard let values = snapshot.value as? [String:Any] else {
        print("データは存在しない")
        return
    }
    values.forEach({ (key,value) in
        guard let record = value as? [String:Any] else { return }
        guard let timeInterval1970 = record[self.keyDate] as? TimeInterval else { return }
        let date = Date(timeIntervalSince1970: timeInterval1970)
        guard let uuid = record[self.keyUUID] as? String else { return }
        // ここに何か処理を書く
    })
}) { (error) in
    print(error.localizedDescription)
}

最後に

ARKitとFirebaseを組み合わせて空間にメッセージを共有できるSNSアプリ ARIZAR を開発しました。
この ARIZAR 開発時に取得完了タイミングがわからなくて調べました。
また時間ができたら他の技術要素でシェアしていきたいと思います。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

iosアプリで1分タイマーを作ってみた

未経験からエンジニアに転職する為アプリをまず一つ作ってみようと思い、1分タイマーアプリを作ってみる事にしました。

バージョン

xcode Version 10.2.1

制作過程

プロジェクトを立ち上げます。
スクリーンショット 2019-07-30 21.11.46.png
Single View APPを使い、次の画面でProductNameなどの必要情報を記載し進める。
スクリーンショット 2019-07-30 21.15.34.png
Main.storyboardにてUILabelを真ん中上に一ヶ所、下部にstart,stop,resetとしてUIButtonを三ヶ所配置する。
スクリーンショット 2019-07-30 21.23.44.png
viewController.swiftファイルにそれぞれを紐付ける。
スクリーンショット 2019-07-30 21.50.30.png
各部品に制約をつける。
今回は簡単にAll Views in View ControllerのAdd Missing Constraintsにて一気に行う。
スクリーンショット 2019-08-01 19.31.28.png
view Controllerにてインスタンスの生成と変数に初期値を代入。

//タイマークラスのインスタンスを生成
var timer = Timer() 
//count変数に初期値0を代入   
var count = 0

スタートボタンの実装

//スタートボタン
@IBAction func timerStartButton(_ sender: Any) {
//invalidateメソッドはオブジェクトからタイマーを削除する
        timer.invalidate()
        timer = Timer.scheduledTimer(timeInterval: 1, target: self, selector:#selector(ViewController.updateTimer),
        userInfo: nil, repeats: true)
 }

scheduledTimerメソッド各引数の役割り

名前 説明
timeInterval ループなら間隔,1度きりなら発動までの秒数
target メソッドを持つオブジェクト
selector 実行するメソッド
userInfo オブジェクトに付ける値
repeats 繰り返し実行するかどうか

ストップボタンの実装

 //ストップボタン
@IBAction func timerStopButton(_ sendr: Any) {
//invalidateメソッドはオブジェクトからタイマーを削除する
        timer.invalidate()
 }

リセットボタンの実装

//リセットボタン
@IBAction func timerResetButton(_ sender: Any) {
//invalidateメソッドはオブジェクトからタイマーを削除する
        timer.invalidate()
//count変数を0にする
        count = 0
//timerCountLabelのtextプロパティへString型にキャストしたcount変数を代入する(表示させる)
        timerCountLabel.text = String(count)
 } 

委譲される側の関数

//移譲される側のメソッド
@objc func updateTimer() {
//countが60に達するまで1ずつ加算されていく
    if count < 60 {
    count += 1
//timerCountLabelのtextプロパティへString型にキャストしたcount変数を代入する(表示させる)
   timerCountLabel.text = String(count)
}

完成

import UIKit

class ViewController: UIViewController {
    //Timerクラスのインスタンスを作成
    var timer = Timer()
    //countに初期値を設定
    var count = 0
    override func viewDidLoad() {
    super.viewDidLoad()
    // Do any additional setup after loading the view.
    }
    //ラベル
    @IBOutlet weak var timerCountLabel: UILabel!
    //スタートボタン
    @IBAction func timerStartButton(_ sender: Any) {
        timer.invalidate()
        timer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(ViewController.updateTimer), userInfo: nil, repeats: true)
    }
    //ストップボタン
    @IBAction func timerStopButton(_ sendr: Any) {
           timer.invalidate()
        }
    //リセットボタン
    @IBAction func timerResetButton(_ sender: Any) {
        timer.invalidate()
        count = 0
        timerCountLabel.text = String(count)
    }
    //移譲される側の関数
    @objc func updateTimer() {
        if count < 60 {
        count += 1
        timerCountLabel.text = String(count)
    }

    }
}

参考サイト
Swift4環境でTimerの使い方と挙動を確認してみた

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Timer優良記事まとめ[Swift]

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Swift】タップ位置から「吹き出し」を表示する

はじめに

チュートリアル機能などでみる「吹き出し」を実装してみました。
今回は、以下の2点の仕様に沿って実装してみます。

  • タップした場所を起点に吹き出しが出る
  • 吹き出しが出る方向を指定できる(8方向)

実装イメージ

簡単ですが吹き出しの構成はこんな感じです
balloonView-image.png

吹き出しView用のサブクラスを用意

このサブクラス内では以下の流れで処理を行います。

init時
- 親View(以降BalloonView)のFrameを決定
- 子View(以降innerView)のFrameを「吹き出しに入れたいView(以降contentView)」のSizeを元に決定
- innerViewに、contentViewをaddSubView

BalloonView.swift
    /// イニシャライズ
    ///
    /// - Parameters:
    ///   - focusPoint: 吹き出しが出る地点(三角形の頂点)
    ///   - contentView: 吹き出しの中に入れたいView(今回は長方形のUILabelを渡しています)
    ///   - color: 吹き出しの色
    ///   - directionType: 吹き出すを出す方向
    ///   - triangleBottomLength: 三角形部分の幅
    ///   - triangleHeight: 三角形部分の高さ
    init(focusPoint: CGPoint, contentView: UIView,
         color: UIColor, directionType: BalloonViewDirectionType,
         triangleBottomLength: CGFloat = 25, triangleHeight: CGFloat = 20) {
        self.color = color
        self.directionType = directionType
        self.triangleBottomLength = triangleBottomLength
        self.triangleHeight = triangleHeight

        let viewSize = directionType.viewSize(contentViewSize: contentView.frame.size, triangleHeight: triangleHeight)
        let viewOrigin = directionType.viewOrigin(focusPoint: focusPoint, viewSize: viewSize)
        let viewFrame = CGRect(origin: viewOrigin, size: viewSize)

        // 吹き出しの内容部分描画用のViewを用意
        let innerViewSize = directionType.innerViewSize(superViewFrame: viewFrame, triangleHeight: triangleHeight)
        let innerViewOrigin = directionType.innerViewOrigin(triangleHeight: triangleHeight)
        innerView = UIView(frame: CGRect(origin: innerViewOrigin, size: innerViewSize))

        super.init(frame: viewFrame)

        // BalloonView自体の背景を透明に(吹き出しのみを見せるため)
        backgroundColor = .clear

        innerView.backgroundColor = color
        addSubview(innerView)
        innerView.addSubview(contentView)
        contentView.center = self.convert(innerView.center, to: innerView)
    }

今回は、「吹き出しを出す方向」を定義するenum(BalloonViewDirectionType)に、諸々のFrame生成処理を任せています。

BalloonViewDirectionType.swift
    /// BalloonView(長方形&三角形を含む)のサイズを返す
    ///
    /// - Parameters:
    ///   - contentViewSize: 吹き出し内に入れたいViewのサイズ
    ///   - triangleHeight: 三角形部分(吹き出し)の高さ
    /// - Returns: BalloonViewのサイズ
    func viewSize(contentViewSize: CGSize, triangleHeight: CGFloat) -> CGSize {
        switch self {
        case .up, .under, .upperRight, .lowerRight, .upperLeft, .lowerLeft:
            return CGSize(width: contentViewSize.width + expandLength.width,
                          height: contentViewSize.height + expandLength.height + triangleHeight)
        case .right, .left:
            return CGSize(width: contentViewSize.width + expandLength.width + triangleHeight,
                          height: contentViewSize.height + expandLength.height)
        }
    }

    /// BalloonView(長方形&三角形を含む)のOriginを返す
    ///
    /// - Parameters:
    ///   - focusPoint: 吹き出しが出る地点
    ///   - viewSize: BalloonViewのサイズ
    /// - Returns: BalloonViewのOrigin
    func viewOrigin(focusPoint: CGPoint, viewSize: CGSize) -> CGPoint {
        switch self {
        case .up:
            return CGPoint(x: focusPoint.x - viewSize.width / 2, y: focusPoint.y - viewSize.height)
        case .under:
            return CGPoint(x: focusPoint.x - viewSize.width / 2, y: focusPoint.y)
        case .right:
            return CGPoint(x: focusPoint.x, y: focusPoint.y - (viewSize.height / 2))
        case .left:
            return CGPoint(x: focusPoint.x - viewSize.width, y: focusPoint.y - (viewSize.height / 2))
        case .upperRight:
            return CGPoint(x: focusPoint.x, y: focusPoint.y - viewSize.height)
        case .lowerRight:
            return focusPoint
        case .upperLeft:
            return CGPoint(x: focusPoint.x - viewSize.width, y: focusPoint.y - viewSize.height)
        case .lowerLeft:
            return CGPoint(x: focusPoint.x - viewSize.width, y: focusPoint.y)
        }
    }

    /// 吹き出し内容描画用Viewのサイズを返す
    ///
    /// - Parameters:
    ///   - superViewFrame: BalloonViewのframe
    ///   - triangleHeight: 三角形部分(吹き出し)の高さ
    /// - Returns: 吹き出し内容描画用Viewのサイズ
    func innerViewSize(superViewFrame: CGRect, triangleHeight: CGFloat) -> CGSize {
        switch self {
        case .up, .under:
            return CGSize(width: superViewFrame.size.width, height: superViewFrame.size.height - triangleHeight)
        case .right, .left:
            return CGSize(width: superViewFrame.size.width - triangleHeight, height: superViewFrame.size.height)
        case .upperRight, .lowerRight, .upperLeft, .lowerLeft:
            return CGSize(width: superViewFrame.width, height: superViewFrame.height - triangleHeight)
        }
    }

    /// 吹き出し内容描画用ViewのOriginを返す
    ///
    /// - Parameter triangleHeight: 三角形部分(吹き出し)の高さ
    /// - Returns: 吹き出し内容描画用ViewのOrigin
    func innerViewOrigin(triangleHeight: CGFloat) -> CGPoint {
        switch self {
        case .up, .left, .upperRight, .upperLeft:
            return .zero
        case .under:
            return CGPoint(x: .zero, y: triangleHeight)
        case .right:
            return CGPoint(x: triangleHeight, y: .zero)
        case .lowerRight, .lowerLeft:
            return CGPoint(x: .zero, y: triangleHeight)
        }
    }

draw時
- UIBezierPathを用いて、三角形部分をBalloonViewに描画する

BalloonView.swift
    override func draw(_ rect: CGRect) {
        super.draw(rect)
        innerView.layer.masksToBounds = true
        innerView.layer.cornerRadius = 10

        // 吹き出しの三角形部分を描画する
        drawBalloonPath(rect: rect)
    }

    /// 吹き出しの三角形部分を描画する
    ///
    /// - Parameter rect: BalloonView自体のFrame
    func drawBalloonPath(rect: CGRect) {
        // 三角形の各頂点を取得
        let cornerPoints = directionType.triangleCornerPoints(superViewRect: rect,
                                                              triangleBottomLength: triangleBottomLength,
                                                              triangleHeight: triangleHeight)
        // 三角形の描画
        let triangle = UIBezierPath()
        triangle.move(to: cornerPoints.left)
        triangle.addLine(to: cornerPoints.top)
        triangle.addLine(to: cornerPoints.right)
        triangle.close()
        // 内側の色をセット
        color.setFill()
        // 内側を塗りつぶす
        triangle.fill()
    }

三角形の各頂点を決定する処理に関しても、BalloonViewDirectionTypeに任せています。

BalloonViewDirectionType.swift
    /// 三角形部分(吹き出し)描画用の頂点(3つ)の座標を返す
    ///
    /// - Parameters:
    ///   - superViewRect: BalloonViewのframe
    ///   - triangleBottomLength: 三角形の底辺の長さ
    ///   - triangleHeight: 三角形の高さ
    /// - Returns: 三角形部分(吹き出し)描画用の頂点(3つ)の座標
    func triangleCornerPoints(superViewRect: CGRect,
                              triangleBottomLength: CGFloat,
                              triangleHeight: CGFloat) -> (top: CGPoint, left: CGPoint, right: CGPoint) {
        let top: CGPoint
        let left: CGPoint
        let right: CGPoint
        let triangleBottomLengthHalf = triangleBottomLength / 2
        // 斜め方向の三角部分の開始位置決定用
        let diagonallyDirectionTriangleBottomCenterX = superViewRect.size.width * 0.2
        let shortLength = diagonallyDirectionTriangleBottomCenterX - triangleBottomLengthHalf
        let longLength = diagonallyDirectionTriangleBottomCenterX + triangleBottomLengthHalf

        switch self {
        case .up:
            top = CGPoint(x: superViewRect.size.width / 2, y: superViewRect.size.height)
            left = CGPoint(x: top.x + triangleBottomLengthHalf, y: top.y - triangleHeight)
            right = CGPoint(x: top.x - triangleBottomLengthHalf, y: left.y)
        case .under:
            top = CGPoint(x: superViewRect.size.width / 2, y: .zero)
            left = CGPoint(x: top.x - triangleBottomLengthHalf, y: top.y + triangleHeight)
            right = CGPoint(x: top.x + triangleBottomLengthHalf, y: left.y)
        case .right:
            top = CGPoint(x: .zero, y: superViewRect.size.height / 2)
            left = CGPoint(x: top.x + triangleHeight, y: top.y + triangleBottomLengthHalf)
            right = CGPoint(x: left.x, y: top.y - triangleBottomLengthHalf)
        case .left:
            top = CGPoint(x: superViewRect.size.width, y: superViewRect.size.height / 2)
            left = CGPoint(x: top.x - triangleHeight, y: top.y - triangleBottomLengthHalf)
            right = CGPoint(x: left.x, y: top.y + triangleBottomLengthHalf)
        case .upperRight:
            top = CGPoint(x: superViewRect.origin.x, y: superViewRect.size.height)
            left = CGPoint(x: top.x + longLength, y: top.y - triangleHeight)
            right = CGPoint(x: top.x + shortLength, y: left.y)
        case .lowerRight:
            top = superViewRect.origin
            left = CGPoint(x: top.x + shortLength, y: top.y + triangleHeight)
            right = CGPoint(x: top.x + longLength, y: left.y)
        case .upperLeft:
            top = CGPoint(x: superViewRect.size.width, y: superViewRect.size.height)
            left = CGPoint(x: top.x - shortLength, y: top.y - triangleHeight)
            right = CGPoint(x: top.x - longLength, y: left.y)
        case .lowerLeft:
            top = CGPoint(x: superViewRect.size.width, y: .zero)
            left = CGPoint(x: top.x - longLength, y: top.y + triangleHeight)
            right = CGPoint(x: top.x - shortLength, y: left.y)
        }

        return (top, left, right)
    }

画面表示

用意した吹き出しView(BalloonView)をViewController側で生成、表示してみます。
今回は、タップ地点からBalloonViewが表示されるようにするため、UITapGestureRecognizerクラスを拡張して吹き出し表示メソッドを持たせています。

ViewController.swift
private extension UITapGestureRecognizer {
    /// タップした場所にBalloonViewを表示する
    ///
    /// - Parameters:
    ///   - color: 吹き出しの色
    ///   - contentView: 吹き出し内に入れたいView
    ///   - directionType: 吹き出しを出したい方向
    func showBalloonView(color: UIColor, contentView: UIView, directionType: BalloonViewDirectionType) {
        guard let tappedView = self.view else { return }

        // 吹き出しの表示数はタップしたView内で1つのみとする
        tappedView.subviews.forEach {
            if $0 is BalloonView {
                $0.removeFromSuperview()
            }
        }

        let tapPosition = self.location(in: tappedView)
        let balloonView = BalloonView(focusPoint: tapPosition,
                                      contentView: contentView,
                                      color: color,
                                      directionType: directionType)
        balloonView.alpha = 0
        tappedView.addSubview(balloonView)

        UIView.animate(withDuration: 0.3) {
            balloonView.alpha = 1.0
        }
    }
}

あとはタップイベントを受けて、用意したメソッドを呼ぶだけです。

ViewController.swift
   @IBAction func tappedRedView(_ sender: UITapGestureRecognizer) {
        let titleLabel = UILabel(frame: CGRect(origin: .zero, size: .zero))
        titleLabel.textAlignment = .center
        titleLabel.text = "こんにちは!"
        titleLabel.sizeToFit()
        sender.showBalloonView(color: .white, contentView: titleLabel, directionType: .up)
    }

balloonView-label

最終的なサンプルでは、4つのタップ領域を用意して、それぞれ違う方向に吹き出しを表示してみています。

代替テキスト

ソースコード

今回作成したサンプルのソースは以下のリポジトリにあります。
https://github.com/ddd503/BalloonView-Sample

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Swift4.2で文字読み上げ機能実装

文字読み上げ機能概要

ボタンを押すと指定した文字を読み上げてくれるシンプルな機能。

version
swift4.2

参考動画

以下のYoutube動画を参考にしました。
https://www.youtube.com/watch?time_continue=1081&v=MO_09UCI5i0

コード

SpeechService.swiftというファイルを用意して、このファイル内で読み上げ機能の設定をします。
languageの箇所を変更すると他の言語で読み上げてくれます。
使える言語はiPhoneにインストールされているもので、日本語、英語、中国語など様々です。

SpeechService.swift
import UIKit
import AVFoundation

class SpeechService {

    private let synthesizer = AVSpeechSynthesizer()
     // 再生速度を設定
    var rate: Float = AVSpeechUtteranceDefaultSpeechRate
    // 言語を英語に設定
    var voice = AVSpeechSynthesisVoice(language: "en-US")

    func say(_ phrase: String) {
        // 話す内容をセット
        let utterance = AVSpeechUtterance(string: phrase)
        utterance.rate = rate
        utterance.voice = voice

        synthesizer.speak(utterance)
    }

    func getVoices() {

        AVSpeechSynthesisVoice.speechVoices().forEach({ print($0.language) })
    }
}

ViewControllerでspeechServiceクラスのsay関数を呼び出す事で引数の文字列を読み上げてくれます。
speechService.say("hoge")とするとhogeと読み上げてくれます。

ViewController.swift
import UIKit
import AVFoundation

class ViewController: UIViewController{

    let speechService = SpeechService()

    override func viewDidLoad() {
        super.viewDidLoad()
    }

    @IBAction func touchedButton(_ sender: Any) {
        speechService.say("hoge")
    }

}

今回は、ViewControllerのtouchedButtonとストーリーボードが紐づいているので、ボタンを押す事で、文字が読み上げられます。

スクリーンショット 2019-07-06 15.27.41.png

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

案件面談時のQ&A・対策したこと(エンジニア編)

自己紹介

こんにちは! iOSエンジニアのやまたつ です☺️
Oshidoriというアプリを個人で開発し、リリースしています!!!
エンジニア歴は1年ちょいくらい。Swift歴は8ヶ月です。

初めて案件面談を受けたので、その備忘録と対策をまとめます。
エンジニアの方の面談の参考になると嬉しいです☺️

結果的にフリーランスとして雇っていただき、現場で日々格闘しています???

対象者

  • 面談を控えているエンジニア
  • フリーランスを目指しているエンジニア
  • 面談対策をしたいエンジニア

参考までに、募集内容を掲載

【必須】
 ・iOSアプリの実務での開発経験
 ・エンジニア実務経験3年以上
【歓迎】
 ・業務アプリ制作経験

上記の条件でした。

対策したこと

以下の動画はオススメです!

業務未経験のWeb系エンジニア志望者が面接で聞かれる頻出質問とその対策

参考になるポイント

  • 面談相手が何を聞きたいのかが分かる
  • どのような心構えで行けば良いのか分かる

一緒に働く相手に求めるものは会社ごとに違うと思いますが、
共通して求めているものは以下だと考えます。

  • コミュニケーション能力
  • 技術力への向上心はあるか
  • 1人で自走できそうか

無論、元々の技術力は求められますが
解決できない課題があったときに、

  • 相談できるコミュニケーション能力
  • 調べまくって解決まで持っていく向上心
  • 諦めずに自走する

以上ができそうかを見られているのではないかなと思います。
上記が伝わるエピソードを積極的に話すと良いですね?

参考にしたサイト

iOSエンジニアがフリーランス案件の面談で良く聞かれる質問集をまとめてみた

iOSアプリエンジニアのための面接質問集100選

すごく良くまとまっていて、本当にありがたかったです!!!
技術的な内容が多く、参考にさせていただきました。

また、記事で分からないところを勉強すれば iOSエンジニアとして一流になれるのではないかと考えました!
ロードマップ的な存在ですね✈️✈️✈️✈️✈️✈️✈️✈️

他の言語のエンジニアの方は、
「自分の言語 面談 聞かれたこと」 例: Ruby 面談 聞かれたこと
で、ググったらロードマップが出てくると思います?

エンジニアの技術力を見られるポイント

この辺を抑えておけば良いかと思います。
実務経験が短い or ない 場合は黙らせるぐらいのアウトプットを出していれば問題ないかと。

  • アーキテクチャの知見
  • API 設計
  • 実務経験(職務履歴書をもとに)
  • 成果物(Qiitaなどアウトプット、個人開発)

履歴書のポイント

履歴書に書くのは今まで触ったことがある言語は全て書いた方がいいみたいです。
実際の業務で使う言語は、自分の得意言語になると思うので(自分の場合はSwift)、とにかく書いた方がいいです。
企業側の安心材料になるみたいです!

ただし、嘘はダメですよ。

以下は、エージェントと話した内容です。(実話)

エージェント「今まで少しでも書いたことがある言語は全て書いてください!
少しでもやったことがあれば、相手の好印象です。今はフルスタックのエンジニアが求められています!」

やまたつ「自信ない言語は書きたくないです。。。」

エージェント「知見があるって言えばいいんですよ。」

やまたつ「なるほど」

【知見がある】って便利やなぁ。。。。 と思いました?

Q&A

1. 今までの開発で一番大変だった経験は何ですか?

アプリを開発するときに、序盤からログインの実装や、DB周りの設計を行ってしまい、途中で機能の変更があったときに修正が大変だったことです。
アプリ開発では、最低限の機能でリリースを目指して検証を行うことが大切だと思いました。また、UIを完成させて、サーバーサイドに取り組む方が結果的に早く実装が進むと考えています。
こちらの記事に大変だったことを書いています?

⭐️体験+改善案を話す

2. 分からないエラーが出てきたときは、どうしていましたか?

エラーの文章を読み込んで、どこに問題があるかを特定します。
全く検討がつかない場合は、エラー文で検索し、答えを探します。
英語で検索した方が良い回答が得られるので、海外のスタックオーバーフローをよく使っています。
それでも分からないときは、知り合いのエンジニアに聞いたり、勉強会に参加して強いエンジニアに聞きたりしています。

⭐️なんとしてでも解決する姿勢を見せる+解決方法を数種類出す

3. どのようなアーキテクチャで開発してきましたか?

今まではMVCで開発してきました。スタートアップでの開発が多かったのでプロトタイプを作るときにMVVMやクリーンアーキテクチャはオーバースペックだと判断したので、MVCで開発していました。
個人開発でも、MVCで開発しています。

⭐️アーキテクチャの知見があるかを話す。何を知っているかを話す。

4. 技術力を向上させるために行っていることはありますか?

個人開発を行っており、自分の知らない技術を調べて実装することで技術力を向上させています。
勉強会の参加や、朝活に参加し、他のエンジニアとの交流を行うことも行っています。
また、Qiitaの記事を書いてアウトプットも行っています。

⭐️自分の行っている活動を正直に話す。アウトプットがあると好印象。個人開発は強い(確信)

5. なぜフリーランスになったのですか?

※ここは参考にならないと思いますが、一応書いております。

会社に所属するときに、必ず「ビジョンとミッションへの共感」を求められますが、共感を強いられることが嫌だからです。
必ず最終面接で「共感していますか?」と聞かれるので、その度に嘘をつきたくないです。

もちろん、ビジョンとミッションに共感することは大事ですが、自分はエンジニアなので技術力の向上に一番力を入れています。
技術力が向上し、プロダクトに愛着があれば良いと思っているので、転職ではなくフリーランスになりました。

⭐️信頼できるエンジニアと思われるような話ができれば良いと思います

6. クリーンアーキテクチャでの開発ですが大丈夫ですか?

クリーンアーキテクチャの経験はありませんが、最近本を読んでいたので概念は理解しております(これは本当)
もしも参画させていただけることになったら、実際にクリーンアーキテクチャでプロダクトを作成してみてキャッチアップしてきます。(アプリ作成済み!近日Qiitaで公開したいと思います☺️)

⭐️足りないところへのキャッチアップの意欲を見せよう

最後の鉄板質問: 「質問はありますか?」

今までの会話の中で疑問に思ったことを話せばOKです!
何も質問がないのはNGです!!!!!!
何も質問がないとき用の無難な質問を載せておきます。
どんどん深掘りしていくと、「うちの案件に興味があるんだな」という印象をつけることができます!

  • 参画前に自前に勉強しておいた方が良いことはなんですか
  • 現場の開発環境はどのようになっていますか
  • 開発の体制はどのようになっていますか
  • 作業の進め方はどのようになっていますか

合格の勝因

個人開発が大きかったと思っています。(もちろん面談対策も効きましたね)
アピールしたところは主に3点です。

  • プッシュ通知や証明書周りもわかる
  • Firebase を使っていてモダンな開発を行っている
  • リリースまでしており、評価が4.9

新しい技術を使っていることや、個人開発でリリースまでやり切ることは、とてもアピールになりました!
自分のプロダクトのアピールポイントを持っておくと良いですね☺️

最後に

極論は、「一緒に働きたいな」と思ってもらえるかどうかです!

特に面談のときに大事にしたいのは、

  • 自信を持って話すこと
    コミュニケーションが取れなさそうと思われてしまいます!
    もごもごならずに、とにかく「〜〜〜です。」まで話すことを意識しましょう!

  • ネガティブに話さないこと
    「〇〇の経験がないですね」と言われたら、「〜〜〜までなら分かります。参画するまでにはキャッチアップしてきます!」
    「〇〇は分かりますか?」 で分からなかったら、「知識不足で分かりません。帰りに調べておきます。」
    などなど。
    知見がなくても、キャッチアップする姿勢を見せましょう。

  • 技術力を向上させる意思があること
    ここは重要だと思います。プロダクトを成長させてくれる仲間が技術力を向上させたくない人だと嫌だと思います。
    何かエピソードを持っていくと良いですね!!!

この記事が誰かのためになってくれると嬉しいです?

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

意図せずObjective-C を読まなければいけなくなった Swift エンジニアへ送る Objective-C読み方まとめ

自己紹介

こんにちは! iOSエンジニアのやまたつ です☺️
Oshidoriというアプリを個人で開発し、リリースしています!!!
8月から参画している現場のプロダクトに Objective-C が4割くらい入っていました!(予想外)
ソースコードを読み解くのに、Objective-C を学ばなければついていけなくなるので勉強中です!

モチベーション

正直、Objective-C を学ぶモチベーションはほぼありません!
というのも、これから新規アプリを作成するときに Objective-C を選択する企業はほとんどないと思うし、Swift の方がモダンで書きやすいからです。

しかし Swift が出てきたのも2014年と最近で、それまでに作られたアプリは Objective-C で書かれています。
昔からある案件では Objective-C で書かれたアプリがまだまだ存在し、Objective-C を読めた方が重宝されます。
私は案件を探すときに、Objective-C を読めるか聞かれたことがあります。
書けるまでは行かなくても読めるようにはなりたいです!
職務経歴書にも Objective-C を書けるようになりますしね!

あと、Swift はわかるけど Objective-C は分からないという人がこの記事を読んで「助かった〜」となってくれると嬉しいです☺️

対象者

  • Swift でアプリ開発をすると思っていたら、現場の言語に Objective-C が潜んでいて困っているエンジニア
  • Objective-C と Swift の書き方比較を見たい人

本題 (これだけ分かればええやろ?というとこだけ)

1. Objective-C にはファイルが2つある

Swiftだとファイル生成するときは、.swift ファイルだけ生成されますが、Objective-C では2つ生成されます。
.h ファイルと、 .m ファイルです。
現場の先輩に聞いた話だと、Objective-C は C言語を参考に作られた言語だからこうなってるらしいです。
C言語も、2つファイルが生成されるんでしょうね! (C++ が2つらしい)

 メモ ?: 「〜.swift」 → 「〜.h」 と 「〜.m」 の2つになる

.h ファイル (ヘッダーファイル)

宣言をするファイルです。
プロパティや、メソッドを宣言します。中身は .m ファイルに書きます。

例えば、以下だけ書いている感じです。中身はないです。
Swift と Objective-C を書いてます。
※Swift にもし .h ファイルあったら、こんな感じでは?という書き方です。

Hoge.h(もしもSwiftで書いたら)
class Hoge: Fuga {
  let hoge: String 
  func getHoge() -> String {}
}
Hoge.h(Objective-C)
@interface Hoge: Fuga
- NSString hoge; 
- (NSString *)getHoge;
@end

※ NSStirng と String は違います。 が、わかりやすくするために書いてます!参考はこちら

.m ファイル (実装ファイル)

.m ファイルで中身を書いていきます!
変数の中身と、メソッドの中身ですね。
Swiftだったら一つのファイルなのに、Objective-C は .hファイル(宣言ファイル)も必要なんですね!

Hoge.m(Swiftならこう書く)
class Hoge: Fuga {
  let hoge: String = "test1"
  func getHoge() -> String {
      return hoge
  }
}
Hoge.m(Objective-C)
@interface Hoge: Fuga 
  NSString *hoge = @"test1"; 
  - (NSString *)getHoge {
     return self.hoge;
  }
@end

 メモ ?: 独特な書き方だけど、 @interface~@end で囲まれていることと、;(セミコロン) で区切られていることを目印にすれば良さげ!

⭐️Tips⭐️

  • .h ファイルに書くのは他のファイルから参照できるようにするプロパティとメソッド。 .mファイルだけに書いたら、privateプロパティ、メソッドになる
  • プロパティに、@property をつけると、getter, setter が付与される
  • @interface~@end がクラスの宣言。
  • @interface~@end の後に、@implementation~@end を書くと、「クラスエクステンション」と呼ばれる機能を使える。クラスを拡張する機能。この機能を利用して疑似的にプライベートにすることが可能。(Swift の Extensionっぽい?)
  • import の対象は、 .h ファイルだけ。実装ファイルでなく、ヘッダーファイルを参照する。

2. メソッドの読み方がちと難しい

Objective-C では クラスのメソッドに - と + をつけます。
違いは、以下です。なるほど。

マイナス(ー)記号ではじまるものは「インスタンスメソッド」
クラスのインスタンスがあって初めて実行できるメソッド

プラス(+)記号で始まるのは「クラスメソッド」
クラスのインスタンスを生成せずに、クラスの型から直接実行することが可能で、クラスのインスタンスを生成して返すメソッドなどで使用されています。

メモ ?: + は Swift の static メソッド

また、メソッドの引数や返り値の書き方が独特な気がします!
以下に、Swift と Objective-C のメソッドの使い方を記述します。
(String = NSString のニュアンスで書いてます)

Swiftのメソッドの基本形態
func メソッド名(引数1の内部の名前: 引数1の型, 引数2の外部の名前 引数2の内部の名前: 引数2の型) ->(返す型) {
   メソッドの中身
}

func getFuga(hoge: String, byPiyo receivePiyo: String) -> String {
   var fuga = hoge + receivePiyo
   return fuga
}

// 呼び出し方
getFuga(hoge: "引数hogeを渡すよ!", byPiyo: "これがPiyoだよ!")

Objective-Cのメソッドの基本形態
-(返す型)メソッド名:(引数1の型) 引数1の内部の名前 引数2の外部の名前:(引数2の型) 引数2の内部の名前 {
   メソッドの中身
}

- (NSString) getFuga: (NSString) hoge byPiyo: (NSString) receivePiyo {
   NSString *fuga = hoge + receivePiyo;
   return fuga;
} 

// 呼び出し方
[self getFuga:@"引数hogeを渡すよ!" byPiyo:@"これがPiyoだよ!"];

⭐️Tips⭐️

  • メソッドを呼び出すときは [ ] で囲まれている
  • メソッドの定義するときは、- か + が付く
  • : で、メソッド名と、引数が分かれている。引数が複数ある場合も、 :(コロン)で分かれている。

3. あとは雰囲気で読めそう(な気がする)

ざっと Objective-C で書かれたコードを見てみた感想は、
「意外と読めそう?」です。

@privateや、@pubic,@protocol などなど、@~~ で書かれているものが何を示しているのかも、なんとなく読むことができると思いました。

自分が Objective-C を調査した結果、ファイルが2つに分かれている点と、メソッドの読み方さえできれば、なんとなく読むことはできる はずです。

データの型だったり、UIKit、NS~~の違いなどもあると思いますが、実際に Objective-C のコードに触れてみてキャッチアップしてみようと思います。

Objective-C を読むことができる諸先輩方へ

「Objective-Cのここは注意しな!」 だったり、「記事のここ全然違うよ。」 というところがあれば、ご教授いただければと思います!
よろしくお願いいたします?‍♂️

参考

Objective-Cまとめ @tomoyuki_okawa さん

Objective-C のメソッド定義の+(プラス)記号

Objective-Cのクラスやプロパティ、特徴的なメソッドの使い方 (3/3)

最後に

現場で読んでみた感想を編集後記として書こうと思います!
次回、「Objective-C を全然読めなかった話」 にならないように頑張ります?

アプリ開発を始める方に読んでほしい記事↓↓↓↓↓↓

〜拝啓〜 アプリ開発を始める半年前の自分へ。アプリ公開初日に300ユーザーを獲得できるぞ!!!

編集後記[WIP]

現場での読んでみた感想を書く予定?

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

意図せずObjective-C を読まなければいけなくなった Swift エンジニアへ贈る Objective-C読み方まとめ

自己紹介

こんにちは! iOSエンジニアのやまたつ です☺️
Oshidoriというアプリを個人で開発し、リリースしています!!!
8月から参画している現場のプロダクトに Objective-C が4割くらい入っていました!(予想外)
ソースコードを読み解くのに、Objective-C を学ばなければついていけなくなるので勉強中です!

モチベーション

正直、Objective-C を学ぶモチベーションはほぼありません!
というのも、これから新規アプリを作成するときに Objective-C を選択する企業はほとんどないと思うし、Swift の方がモダンで書きやすいからです。

しかし Swift が出てきたのも2014年と最近で、それまでに作られたアプリは Objective-C で書かれています。
昔からある案件では Objective-C で書かれたアプリがまだまだ存在し、Objective-C を読めた方が重宝されます。
私は案件を探すときに、Objective-C を読めるか聞かれたことがあります。
書けるまでは行かなくても読めるようにはなりたいです!
職務経歴書にも Objective-C を書けるようになりますしね!

あと、Swift はわかるけど Objective-C は分からないという人がこの記事を読んで「助かった〜」となってくれると嬉しいです☺️

対象者

  • Swift でアプリ開発をすると思っていたら、現場の言語に Objective-C が潜んでいて困っているエンジニア
  • Objective-C と Swift の書き方比較を見たい人

本題 (これだけ分かればええやろ?というとこだけ)

1. Objective-C にはファイルが2つある

Swiftだとファイル生成するときは、.swift ファイルだけ生成されますが、Objective-C では2つ生成されます。
.h ファイルと、 .m ファイルです。
現場の先輩に聞いた話だと、Objective-C は C言語を参考に作られた言語だからこうなってるらしいです。
C言語も、2つファイルが生成されるんでしょうね! (C++ が2つらしい)

 メモ ?: 「〜.swift」 → 「〜.h」 と 「〜.m」 の2つになる

.h ファイル (ヘッダーファイル)

宣言をするファイルです。
プロパティや、メソッドを宣言します。中身は .m ファイルに書きます。

例えば、以下だけ書いている感じです。中身はないです。
Swift と Objective-C を書いてます。
※Swift にもし .h ファイルあったら、こんな感じでは?という書き方です。

Hoge.h(もしもSwiftで書いたら)
class Hoge: Fuga {
  let hoge: String 
  func getHoge() -> String {}
}
Hoge.h(Objective-C)
@interface Hoge: Fuga
- NSString hoge; 
- (NSString *)getHoge;
@end

※ NSStirng と String は違います。 が、わかりやすくするために書いてます!参考はこちら

.m ファイル (実装ファイル)

.m ファイルで中身を書いていきます!
変数の中身と、メソッドの中身ですね。
Swiftだったら一つのファイルなのに、Objective-C は .hファイル(宣言ファイル)も必要なんですね!

Hoge.m(Swiftならこう書く)
class Hoge: Fuga {
  let hoge: String = "test1"
  func getHoge() -> String {
      return hoge
  }
}
Hoge.m(Objective-C)
@interface Hoge: Fuga 
  NSString *hoge = @"test1"; 
  - (NSString *)getHoge {
     return self.hoge;
  }
@end

 メモ ?: 独特な書き方だけど、 @interface~@end で囲まれていることと、;(セミコロン) で区切られていることを目印にすれば良さげ!

⭐️Tips⭐️

  • .h ファイルに書くのは他のファイルから参照できるようにするプロパティとメソッド。 .mファイルだけに書いたら、privateプロパティ、メソッドになる
  • プロパティに、@property をつけると、getter, setter が付与される
  • @interface~@end がクラスの宣言。
  • @interface~@end の後に、@implementation~@end を書くと、「クラスエクステンション」と呼ばれる機能を使える。クラスを拡張する機能。この機能を利用して疑似的にプライベートにすることが可能。(Swift の Extensionっぽい?)
  • import の対象は、 .h ファイルだけ。実装ファイルでなく、ヘッダーファイルを参照する。

2. メソッドの読み方がちと難しい

Objective-C では クラスのメソッドに - と + をつけます。
違いは、以下です。なるほど。

マイナス(ー)記号ではじまるものは「インスタンスメソッド」
クラスのインスタンスがあって初めて実行できるメソッド

プラス(+)記号で始まるのは「クラスメソッド」
クラスのインスタンスを生成せずに、クラスの型から直接実行することが可能で、クラスのインスタンスを生成して返すメソッドなどで使用されています。

メモ ?: + は Swift の static メソッド

また、メソッドの引数や返り値の書き方が独特な気がします!
以下に、Swift と Objective-C のメソッドの使い方を記述します。
(String = NSString のニュアンスで書いてます)

Swiftのメソッドの基本形態
func メソッド名(引数1の内部の名前: 引数1の型, 引数2の外部の名前 引数2の内部の名前: 引数2の型) ->(返す型) {
   メソッドの中身
}

func getFuga(hoge: String, byPiyo receivePiyo: String) -> String {
   var fuga = hoge + receivePiyo
   return fuga
}

// 呼び出し方
getFuga(hoge: "引数hogeを渡すよ!", byPiyo: "これがPiyoだよ!")

Objective-Cのメソッドの基本形態
-(返す型)メソッド名:(引数1の型) 引数1の内部の名前 引数2の外部の名前:(引数2の型) 引数2の内部の名前 {
   メソッドの中身
}

- (NSString) getFuga: (NSString) hoge byPiyo: (NSString) receivePiyo {
   NSString *fuga = hoge + receivePiyo;
   return fuga;
} 

// 呼び出し方
[self getFuga:@"引数hogeを渡すよ!" byPiyo:@"これがPiyoだよ!"];

⭐️Tips⭐️

  • メソッドを呼び出すときは [ ] で囲まれている
  • メソッドの定義するときは、- か + が付く
  • : で、メソッド名と、引数が分かれている。引数が複数ある場合も、 :(コロン)で分かれている。

3. あとは雰囲気で読めそう(な気がする)

ざっと Objective-C で書かれたコードを見てみた感想は、
「意外と読めそう?」です。

@privateや、@pubic,@protocol などなど、@~~ で書かれているものが何を示しているのかも、なんとなく読むことができると思いました。

自分が Objective-C を調査した結果、ファイルが2つに分かれている点と、メソッドの読み方さえできれば、なんとなく読むことはできる はずです。

データの型だったり、UIKit、NS~~の違いなどもあると思いますが、実際に Objective-C のコードに触れてみてキャッチアップしてみようと思います。

Objective-C を読むことができる諸先輩方へ

「Objective-Cのここは注意しな!」 だったり、「記事のここ全然違うよ。」 というところがあれば、ご教授いただければと思います!
よろしくお願いいたします?‍♂️

参考

Objective-Cまとめ @tomoyuki_okawa さん

Objective-C のメソッド定義の+(プラス)記号

Objective-Cのクラスやプロパティ、特徴的なメソッドの使い方 (3/3)

最後に

現場で読んでみた感想を編集後記として書こうと思います!
次回、「Objective-C を全然読めなかった話」 にならないように頑張ります?

アプリ開発を始める方に読んでほしい記事↓↓↓↓↓↓

〜拝啓〜 アプリ開発を始める半年前の自分へ。アプリ公開初日に300ユーザーを獲得できるぞ!!!

編集後記[WIP]

現場での読んでみた感想を書く予定?

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

UserDefaultsを使って簡単なログイン機能

初心者です。ログイン機能を作ったので、メモとして残す。

・Rxを使って文字を更新を感知して、ログインボタンのステータスを変える。あと文字数も制限。

スクリーンショット 2019-08-04 2.49.56.png

スクリーンショット 2019-08-04 2.50.13.png

LoginViewController.swift
import UIKit
import RxSwift
import RxCocoa

final class LoginViewController: UIViewController {

    @IBOutlet weak var passwordTextField: UITextField!
    @IBOutlet weak var nameTextField: UITextField!
    @IBOutlet weak var loginButton: UIButton!
    private let disposeBag = DisposeBag()
    private let userdefaulse = UserDefaults.standard

    override func viewDidLoad() {
        super.viewDidLoad()
        initView()
    }

    private func initView() {
        TextFieldObserver(nameTextField)
        TextFieldObserver(passwordTextField)
        ChangeLoginbutton()
    }

    private func TextFieldObserver(_ textField: UITextField) {
        textField.rx.text.subscribe(onNext: { text in
            if let text = text {
                textField.text = text.prefix(15).description
            }
            self.ChangeState()
        }).disposed(by: disposeBag)
    }

    private func ChangeLoginbutton() {
        loginButton.addTarget(self, action: #selector(Login), for: .touchUpInside)
        loginButton.setTitleColor(.white, for: .normal)
        loginButton.setTitleColor(.darkGray, for: .disabled)
    }

    private func ChangeState() {
        if nameTextField.text?.isEmpty == false,
           passwordTextField.text?.isEmpty == false {
            loginButton.isEnabled = true
            loginButton.backgroundColor = UIColor(hex: "b8860b")
        } else {
            loginButton.isEnabled = false
            loginButton.backgroundColor = UIColor(hex: "708090")
        }
    }

    @IBAction private func TransitonToSignUp(_ sender: UIButton) {
        self.performSegue(withIdentifier: "SignUpView", sender: nil)
    }

    @objc private func Login() {

        guard let name = nameTextField.text else { return }
        guard let pw = passwordTextField.text else { return }

        let userName = userdefaulse.string(forKey: "name")
        let userPw = userdefaulse.string(forKey: "password")

        if name == userName && pw == userPw {

            userdefaulse.set(true, forKey: "userLoginState")
            let alert = UIAlertController(title: "成功", message: "ログインしました", preferredStyle:.alert)
            let action = UIAlertAction(title: "OK", style: .default) {
                action in self.performSegue(withIdentifier: "TopView", sender: nil)
            }
            alert.addAction(action)
            self.present(alert,animated: true)
        } else {
            let alert = UIAlertController(title: "警告", message: "名前かパスワードが違います", preferredStyle: .alert)
            let action = UIAlertAction(title: "OK", style: .default)
            alert.addAction(action)
            self.present(alert, animated: true)
        }
    }
}

スクリーンショット 2019-08-04 2.50.24.png

SignUpViewController.swift
import UIKit
import RxCocoa
import RxSwift

final class SignUpViewController: UIViewController {

    @IBOutlet weak var NameTextField: UITextField!
    @IBOutlet weak var PassWordTextField: UITextField!
    @IBOutlet weak var PassWordConfirmationTextField: UITextField!
    @IBOutlet weak var signUpButton: UIButton!
    private let disposeBag = DisposeBag()
    private let userdefaulse = UserDefaults.standard

    override func viewDidLoad() {
        super.viewDidLoad()
        initView()
    }

    private func initView() {
        TextFieldObserver(NameTextField)
        TextFieldObserver(PassWordTextField)
        TextFieldObserver(PassWordConfirmationTextField)
        ChangeSignUpButton()
        ChangeState()

    }

    private func TextFieldObserver(_ textField: UITextField) {
        textField.rx.text.subscribe(onNext: { text in
            if let text = text {
                textField.text = text.prefix(15).description
            }
            self.ChangeState()
        }).disposed(by: disposeBag)
    }

    private func ChangeSignUpButton() {
        signUpButton.addTarget(self, action: #selector(SignUp), for: .touchUpInside)
        signUpButton.setTitleColor(.white, for: .normal)
        signUpButton.setTitleColor(.darkGray, for: .disabled)
    }

    private func ChangeState() {
        if NameTextField.text?.isEmpty == false,
           PassWordTextField.text?.isEmpty == false,
           PassWordConfirmationTextField.text?.isEmpty == false {
            signUpButton.isEnabled = true
            signUpButton.backgroundColor = UIColor(hex: "1e90ff")
        } else {
            signUpButton.isEnabled = false
            signUpButton.backgroundColor = UIColor(hex: "708090")
        }
    }

    @objc private func SignUp() {

        guard let name = NameTextField.text else { return }
        guard let pw = PassWordTextField.text else { return }
        guard let pwc = PassWordConfirmationTextField.text else { return }

        if pw != pwc{
            let alert = UIAlertController(title: "警告", message: "パスワードが一致しません", preferredStyle: .alert)
            let action = UIAlertAction(title: "OK", style: .default)
            alert.addAction(action)
            self.present(alert, animated: true)
            return
        }

        userdefaulse.set(name, forKey: "name")
        userdefaulse.set(pw, forKey: "password")
        userdefaulse.set(true, forKey: "userLoginState")
        let alert = UIAlertController(title: "成功", message: "登録が成功しました", preferredStyle: .alert)
        let action = UIAlertAction(title: "OK", style: .default) {
            action in self.dismiss(animated: true, completion: nil)
        }
        alert.addAction(action)
        self.present(alert, animated: true)
    }
}

一旦、ログアウトだけ。
ここからチャット機能を追加したい。
ログアウト押すとuserdefaultsにfalseをセットする。

スクリーンショット 2019-08-04 2.49.42.png

ViewController.swift
import UIKit

class ViewController: UIViewController {

    let userdefaults = UserDefaults.standard
    override func viewDidLoad() {
        super.viewDidLoad()

    }
    override func viewDidAppear(_ animated: Bool) {
        let userLoginState = userdefaults.bool(forKey: "userLoginState")
        print(userLoginState)
        if (!userLoginState) {
            self.performSegue(withIdentifier: "loginView", sender: nil)
        }
    }
    @IBAction func Logout(_ sender: UIButton) {
        userdefaults.set(false, forKey: "userLoginState")
        self.performSegue(withIdentifier: "loginView", sender: nil)
    }
}

ここは写経。
時間があるときに、じっくり調べる。

UIColor.Swift
import UIKit

extension UIColor {

    convenience init(hex: String, alpha: CGFloat) {
        let v = Int("000000" + hex, radix: 16) ?? 0
        let r = CGFloat(v / Int(powf(256, 2)) % 256) / 255
        let g = CGFloat(v / Int(powf(256, 1)) % 256) / 255
        let b = CGFloat(v / Int(powf(256, 0)) % 256) / 255
        self.init(red: r, green: g, blue: b, alpha: min(max(alpha,0 ),1))
    }

    convenience init(hex: String) {
        self.init(hex: hex, alpha: 1.0)
    }
}

参考記事
RxSwift
https://qiita.com/Narakkyyyyy/items/39e76cfd188b1595293f
Userdefaults
https://qiita.com/TakayukiNJ/items/63ade09400db8593eff0

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Swift】最前面のViewControllerの取得方法(忘備録)

最前面のViewControllerの取得方法が、しばしば記憶から消えるので忘備録的に。

お手軽パターン

ViewControllerExtension.swift
func topViewController() -> UIViewController? {
        var vc = UIApplication.shared.keyWindow?.rootViewController
        while vc?.presentedViewController != nil {
            vc = vc?.presentedViewController
        }
        return vc
    }

大体の場合、Google先生に聞いた時はこちらが出てくる。
presentでViewControllerをどんどん重ねていく系のアプリではこれでも問題がない。

が、NavigationControllerやTabBarControllerを使っている場合、それらの中身までは判定できない。
つまり、返り値がNavigationControllerやTabBarControllerになってしまう。
大抵の場合、それではNGなことが多いので次の方法を考える。

少し改善したパターン

ViewControllerExtension.swift
func topViewController(controller: UIViewController?) -> UIViewController? {
        if let tabController = controller as? UITabBarController {
            if let selected = tabController.selectedViewController {
                return topViewController(controller: selected)
            }
        }

        if let navigationController = controller as? UINavigationController {
            return topViewController(controller: navigationController.visibleViewController)
        }

        if let presented = controller?.presentedViewController {
            return topViewController(controller: presented)
        }

        return controller
    }

こちらのパターンだと、NavigationControllerやTabBarControllerに当たっても、その中身までを吟味するため正確なViewControllerを取得することができる。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Xcode】ジャンプバーにアノテーションを追加するには

この記事は何

ソースエディタでは、コードに アノテーション(注釈)を付けることができます。
アノテーションには以下の種類があります。
- Todo
- バグ修正リマインダ
- セクション見出し
- セパレータ

アノテーションはジャンプバーのシンボルポップアップメニューに追加されます。

スクリーンショット 2019-08-04 0.05.52.png

環境

macOS Mojave
Xcode11 beta

書き方

to-do アイテムを追加する

Insert a comment with the prefix TODO:.

Todo
// TODO: [your to-do item]

bug fix リマインダを追加する

Insert a comment with the prefix FIXME:.

// FIXME: [your bug fix reminder].

heading を追加する

MARK: プレフィックスを使うと、いわゆる 見出し を表示できます。
ポップアップでは太字になります。

heading
// MARK: [your section heading].

セパレータを追加する

アノテーションの上部に分割線を表示します。
アノテーションコメントに - をつけて、開始します。

separator_on_above
// MARK: - [your content].

アノテーションの下部に分割線を表示します。
アノテーションコメントの最後に - をつけます。

separator_on_below
// MARK: [your content] -.
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む