- 投稿日:2019-08-04T17:11:06+09:00
iosアプリで1分タイマーを作ってみた
未経験からエンジニアに転職する為アプリをまず一つ作ってみようと思い、1分タイマーアプリを作ってみる事にしました。
バージョン
xcode Version 10.2.1
制作過程
プロジェクトを立ち上げます。
Single View APPを使い、次の画面でProductNameなどの必要情報を記載し進める。
Main.storyboardにてUILabelを真ん中上に一ヶ所、下部にstart,stop,resetとしてUIButtonを三ヶ所配置する。
viewController.swiftファイルにそれぞれを紐付ける。
各部品に制約をつける。
今回は簡単にAll Views in View ControllerのAdd Missing Constraintsにて一気に行う。
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) } } }
- 投稿日:2019-08-04T13:08:12+09:00
【Swift】タップ位置から「吹き出し」を表示する
はじめに
チュートリアル機能などでみる「吹き出し」を実装してみました。
今回は、以下の2点の仕様に沿って実装してみます。
- タップした場所を起点に吹き出しが出る
- 吹き出しが出る方向を指定できる(8方向)
実装イメージ
吹き出しView用のサブクラスを用意
このサブクラス内では以下の流れで処理を行います。
init時
- 親View(以降BalloonView)のFrameを決定
- 子View(以降innerView)のFrameを「吹き出しに入れたいView(以降contentView)」のSizeを元に決定
- innerViewに、contentViewをaddSubViewBalloonView.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.swiftoverride 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.swiftprivate 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) }最終的なサンプルでは、4つのタップ領域を用意して、それぞれ違う方向に吹き出しを表示してみています。
ソースコード
今回作成したサンプルのソースは以下のリポジトリにあります。
https://github.com/ddd503/BalloonView-Sample
- 投稿日:2019-08-04T09:00:01+09:00
案件面談時のQ&A・対策したこと(エンジニア編)
自己紹介
こんにちは! iOSエンジニアのやまたつ です☺️
Oshidoriというアプリを個人で開発し、リリースしています!!!
エンジニア歴は1年ちょいくらい。Swift歴は8ヶ月です。初めて案件面談を受けたので、その備忘録と対策をまとめます。
エンジニアの方の面談の参考になると嬉しいです☺️人生初、案件面談がありました!
— やまたつ(25)@Oshidori開発者_Swiftエンジニア (@ta__ta01) July 17, 2019
代表とCTOと面談!
フリーランスとしてなので厳しい質問が来るかなと思ったけど特に厳しいのはなかった。
個人開発のウケが良かった!
4.9の評価やプッシュ通知がついてるところが評価ポイント。先ほど合格通知が来て、来週の月曜から参画が決まりました!やったね☺️結果的にフリーランスとして雇っていただき、現場で日々格闘しています???
対象者
- 面談を控えているエンジニア
- フリーランスを目指しているエンジニア
- 面談対策をしたいエンジニア
参考までに、募集内容を掲載
【必須】
・iOSアプリの実務での開発経験
・エンジニア実務経験3年以上
【歓迎】
・業務アプリ制作経験上記の条件でした。
対策したこと
以下の動画はオススメです!
業務未経験のWeb系エンジニア志望者が面接で聞かれる頻出質問とその対策
参考になるポイント
- 面談相手が何を聞きたいのかが分かる
- どのような心構えで行けば良いのか分かる
一緒に働く相手に求めるものは会社ごとに違うと思いますが、
共通して求めているものは以下だと考えます。
- コミュニケーション能力
- 技術力への向上心はあるか
- 1人で自走できそうか
無論、元々の技術力は求められますが
解決できない課題があったときに、
- 相談できるコミュニケーション能力
- 調べまくって解決まで持っていく向上心
- 諦めずに自走する
以上ができそうかを見られているのではないかなと思います。
上記が伝わるエピソードを積極的に話すと良いですね?参考にしたサイト
iOSエンジニアがフリーランス案件の面談で良く聞かれる質問集をまとめてみた
すごく良くまとまっていて、本当にありがたかったです!!!
技術的な内容が多く、参考にさせていただきました。また、記事で分からないところを勉強すれば iOSエンジニアとして一流になれるのではないかと考えました!
ロードマップ的な存在ですね✈️✈️✈️✈️✈️✈️✈️✈️他の言語のエンジニアの方は、
「自分の言語 面談 聞かれたこと」 例: Ruby 面談 聞かれたこと
で、ググったらロードマップが出てくると思います?エンジニアの技術力を見られるポイント
この辺を抑えておけば良いかと思います。
実務経験が短い or ない 場合は黙らせるぐらいのアウトプットを出していれば問題ないかと。
- アーキテクチャの知見
- API 設計
- 実務経験(職務履歴書をもとに)
- 成果物(Qiitaなどアウトプット、個人開発)
履歴書のポイント
履歴書に書くのは今まで触ったことがある言語は全て書いた方がいいみたいです。
実際の業務で使う言語は、自分の得意言語になると思うので(自分の場合はSwift)、とにかく書いた方がいいです。
企業側の安心材料になるみたいです!ただし、嘘はダメですよ。
以下は、エージェントと話した内容です。(実話)
エージェント「今まで少しでも書いたことがある言語は全て書いてください!
少しでもやったことがあれば、相手の好印象です。今はフルスタックのエンジニアが求められています!」やまたつ「自信ない言語は書きたくないです。。。」
エージェント「知見があるって言えばいいんですよ。」
やまたつ「なるほど」
【知見がある】って便利やなぁ。。。。 と思いました?
Q&A
1. 今までの開発で一番大変だった経験は何ですか?
アプリを開発するときに、序盤からログインの実装や、DB周りの設計を行ってしまい、途中で機能の変更があったときに修正が大変だったことです。
アプリ開発では、最低限の機能でリリースを目指して検証を行うことが大切だと思いました。また、UIを完成させて、サーバーサイドに取り組む方が結果的に早く実装が進むと考えています。
こちらの記事に大変だったことを書いています?⭐️体験+改善案を話す
2. 分からないエラーが出てきたときは、どうしていましたか?
エラーの文章を読み込んで、どこに問題があるかを特定します。
全く検討がつかない場合は、エラー文で検索し、答えを探します。
英語で検索した方が良い回答が得られるので、海外のスタックオーバーフローをよく使っています。
それでも分からないときは、知り合いのエンジニアに聞いたり、勉強会に参加して強いエンジニアに聞きたりしています。⭐️なんとしてでも解決する姿勢を見せる+解決方法を数種類出す
3. どのようなアーキテクチャで開発してきましたか?
今まではMVCで開発してきました。スタートアップでの開発が多かったのでプロトタイプを作るときにMVVMやクリーンアーキテクチャはオーバースペックだと判断したので、MVCで開発していました。
個人開発でも、MVCで開発しています。⭐️アーキテクチャの知見があるかを話す。何を知っているかを話す。
4. 技術力を向上させるために行っていることはありますか?
個人開発を行っており、自分の知らない技術を調べて実装することで技術力を向上させています。
勉強会の参加や、朝活に参加し、他のエンジニアとの交流を行うことも行っています。
また、Qiitaの記事を書いてアウトプットも行っています。⭐️自分の行っている活動を正直に話す。アウトプットがあると好印象。個人開発は強い(確信)
5. なぜフリーランスになったのですか?
※ここは参考にならないと思いますが、一応書いております。
会社に所属するときに、必ず「ビジョンとミッションへの共感」を求められますが、共感を強いられることが嫌だからです。
必ず最終面接で「共感していますか?」と聞かれるので、その度に嘘をつきたくないです。もちろん、ビジョンとミッションに共感することは大事ですが、自分はエンジニアなので技術力の向上に一番力を入れています。
技術力が向上し、プロダクトに愛着があれば良いと思っているので、転職ではなくフリーランスになりました。⭐️信頼できるエンジニアと思われるような話ができれば良いと思います
6. クリーンアーキテクチャでの開発ですが大丈夫ですか?
クリーンアーキテクチャの経験はありませんが、最近本を読んでいたので概念は理解しております(これは本当)
もしも参画させていただけることになったら、実際にクリーンアーキテクチャでプロダクトを作成してみてキャッチアップしてきます。(アプリ作成済み!近日Qiitaで公開したいと思います☺️)クリーンアーキテクチャの話も出てきて、本読んでて良かった〜ってなった。ばーちーさん(@nanamoto7483 )が「これ読んだ方がいいよ」って本を貸してくださったおかげだ。。。ばーちーさんには何から何までお世話になった。。。ありがとうございます?
— やまたつ(25)@Oshidori開発者_Swiftエンジニア (@ta__ta01) July 17, 2019⭐️足りないところへのキャッチアップの意欲を見せよう
最後の鉄板質問: 「質問はありますか?」
今までの会話の中で疑問に思ったことを話せばOKです!
何も質問がないのはNGです!!!!!!
何も質問がないとき用の無難な質問を載せておきます。
どんどん深掘りしていくと、「うちの案件に興味があるんだな」という印象をつけることができます!
- 参画前に自前に勉強しておいた方が良いことはなんですか
- 現場の開発環境はどのようになっていますか
- 開発の体制はどのようになっていますか
- 作業の進め方はどのようになっていますか
合格の勝因
個人開発が大きかったと思っています。(もちろん面談対策も効きましたね)
アピールしたところは主に3点です。
- プッシュ通知や証明書周りもわかる
- Firebase を使っていてモダンな開発を行っている
- リリースまでしており、評価が4.9
新しい技術を使っていることや、個人開発でリリースまでやり切ることは、とてもアピールになりました!
自分のプロダクトのアピールポイントを持っておくと良いですね☺️最後に
極論は、「一緒に働きたいな」と思ってもらえるかどうかです!
特に面談のときに大事にしたいのは、
自信を持って話すこと
コミュニケーションが取れなさそうと思われてしまいます!
もごもごならずに、とにかく「〜〜〜です。」まで話すことを意識しましょう!ネガティブに話さないこと
「〇〇の経験がないですね」と言われたら、「〜〜〜までなら分かります。参画するまでにはキャッチアップしてきます!」
「〇〇は分かりますか?」 で分からなかったら、「知識不足で分かりません。帰りに調べておきます。」
などなど。
知見がなくても、キャッチアップする姿勢を見せましょう。技術力を向上させる意思があること
ここは重要だと思います。プロダクトを成長させてくれる仲間が技術力を向上させたくない人だと嫌だと思います。
何かエピソードを持っていくと良いですね!!!この記事が誰かのためになってくれると嬉しいです?
- 投稿日:2019-08-04T02:06:18+09:00
【Swift】最前面のViewControllerの取得方法(忘備録)
最前面のViewControllerの取得方法が、しばしば記憶から消えるので忘備録的に。
お手軽パターン
ViewControllerExtension.swiftfunc 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.swiftfunc 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を取得することができる。







