はじめに
こんにちは!!Life is Tech!でメンターをしているえーえすです!
Life is Tech ! Kanto Advent Calendar 2022の5日目ということで今回は「カウントアプリを使ってプログラミング勉強にくっそ役立つ問題集を作ってみよう!」という記事を書いてみようと思います。
カウントアプリってなに?
こんな感じで、+ボタンを押せば数字が1増え、ーボタンを押せば数字が1減るというめちゃ単純アプリです。めちゃ単純ですが、ボタンが押されたことを知る、それを変数に反映させる、変数をラベルに表示するといった、アプリ開発の上ですっごい基礎になる概念が十分に取り込まれています。僕の師匠はこれを作らせるのが大好きすぎて毎スクールでこれをメンバーに作らせています。
やりたいこと
さてさてそんなカウントアプリですが、ただこれを作り続けててもつまらないっすよね。もっとバリエーションを持たせれば、よく理解してるカウントアプリの構造上で、さまざまな概念を手を動かしながら学ぶことができるんじゃないかな。そんなことを思って、最近カウントアプリ亜種の問題をたくさん作ってます。
ということで、今まで作ってきたカウントアプリ問題集をここから書いていきます。
初級編 - カウントアプリを作ろう -
カウントアプリ
オーソドックスなカウントアプリ。素材の味そのまま。
コードはこんな感じ。基礎に忠実に。
var num: Int = 0
@IBOutlet weak var numberLabel: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
@IBAction func minusButtonTapped(_ sender: Any) {
num -= 1
updateNumberLabel()
}
@IBAction func plusButtonTapped(_ sender: Any) {
num += 1
updateNumberLabel()
}
func updateNumberLabel() {
numberLabel.text = String(num)
}
updateNumberLabel()
とまとめるのは若干巧いやり方ですが、こんなのでもいいでしょう。
カウントアプリ - 掛け算エディション -
var num: Int = 0
@IBOutlet weak var label: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
updateLabel()
}
@IBAction func minusButtonTapped(_ sender: Any) {
num -= 1
updateLabel()
}
@IBAction func plusButtonTapped(_ sender: Any) {
num += 1
updateLabel()
}
@IBAction func divideButtonTapped(_ sender: Any) {
num /= 2
updateLabel()
}
@IBAction func multipleButtonTapped(_ sender: Any) {
num *= 2
updateLabel()
}
@IBAction func clearButtonTapped(_ sender: Any) {
num = 0
updateLabel()
}
func updateNumberLabel() {
label.text = String(num)
}
この辺まで来るとupdateNumberLabel
を作る価値がわかりやすくなってきます。
カウントアプリ - 色を変えよう -
数字>10で赤色に、<-10で青に、それ以外なら黒に、というやつです。if else
のお勉強ができていい感じです。
...
func updateNumberLabel() {
if num > 10 {
numberLabel.textColor = .red
}else if num < -10 {
numberLabel.textColor = .blue
}else {
numberLabel.textColor = .black
}
numberLabel.text = String(num)
}
カウントアプリ - 手書き -
紙にコードを書いてみましょう。案外@IBOutlet
とか変数の定義の方法とか忘れてるものです。
初中級編 - カウントアプリを発展させてみよう -
さて、カウントアプリの構造が大体わかってきたら、この構造を応用して他のアプリを作ってみましょう。
電卓もどき
押された数字を足していく、みたいなアプリです。電卓やると入力とか演算の部分がちょっと大変だからこれくらいでもいいかなと思います。
電卓
カラーピッカー
RGBを+-で調節して、その色が上に出てくるというアプリです。labelに色表示するのが難しいからそこは教えてあげていいでしょう。
カラーピッカー発展
スライダーでRGBを選択、上にRGBが出てきて、色が表示されるアプリです。実用的。スライダー周りとかがちょっと難しいかな?って感じです。
中級編 - 文法を学ぼう -
さてさてこんな感じで何をやればいいかの構造がわかってきたら、次は途中まで完成しているコードに加筆してカウントアプリを完成させるというのをやってみたいなと思います。
この途中まで完成しているところに、ちょっと難しいエッセンスが詰まってるので、それをうまく解釈しながらカウントアプリの慣れ親しんだ構造に沿うようにコードを書き加える感じです。コードを読む練習にもなって一石二鳥。
カウントアプリ - sender -
senderを使うと同じようなボタンの処理をまとめられていい感じです。
...
@IBAction func buttonPressed(_ sender: UIButton) {
updateNumLabel()
}
これだけ書いてあるので、ここに1行追加してうまく動くようにします。
@IBAction func buttonPressed(_ sender: UIButton) {
num += sender.tag
updateNumLabel()
}
こんな感じ。
電卓とかになるとより有用なテクニックですね。
カウントアプリ - class & struct -
...
@IBAction func plusButtonPressed() {
count.num += 1
label.text = String(count.num)
}
これに合うようにコードを足しましょう。
class Count {
var num: Int = 0
}
こんなのを作ればいいですね。
カウントアプリ - class VS struct -
以下のコードを打つとバグると思います。それを直そう。
class ViewController: UIViewController {
var myCount = Count()
let countManager = CountManager()
override func viewDidLoad() {
super.viewDidLoad()
}
@IBAction func plusButtonPressed() {
countManager.plus(count: myCount)
}
}
struct CountManager {
func plus(count: Count) {
count.num += 1
}
}
struct Count {
var num: Int = 0
func plus() {
num += 1
}
}
class Count {
var num: Int = 0
func plus() {
num += 1
}
}
classにすればcountManagerのほうで引数で同じものを引っ張ってこれるからいい感じになりますね。myCountとcountManagerはletでもいいのか?とかも問題になりそうですね。
struct Count {
var num: Int = 0
mutating func plus() {
num += 1
}
}
mutatingつけちゃってこっちのplus()呼び出せばうまくいきますね。
カウントアプリ - addSubview -
+ボタンを押すたび、ランダムな大きさの四角がランダムな位置に表示される。
addSubview()という存在を知っていると開発への理解が結構深まると思うので、調べながらやるとかいいんじゃ無いかなと思います。
@IBAction func addButtonPressed(_ sender: Any) {
let rect = UIView()
rect.frame = CGRect(origin: CGPoint(x: CGFloat.random(in: 0..<view.bounds.width),
y: CGFloat.random(in: 0..<view.bounds.height)),
size: CGSize(width: CGFloat.random(in: 30..<60),
height: CGFloat.random(in: 30..<60)))
rect.backgroundColor = UIColor.red
view.addSubview(rect)
}
もうちょい可読性あげたいな…
func getRandomPoint() -> CGPoint {
return CGPoint(x: CGFloat.random(in: 0..<view.bounds.width), y: CGFloat.random(in: 0..<view.bounds.height))
}
func getRandomSize() -> CGSize {
return CGSize(width: CGFloat.random(in: 30..<60), height: CGFloat.random(in: 30..<60))
}
こんな関数でframe指定したらいいんじゃないかなと思います。
上級編 - 開発の実践的知識を目指して -
文法に限らず、いろんな概念を獲得できたらいいなぁと思います。
カウントアプリ - MVC -
MVCをちゃんと分離したカウントを作ってみよう。
...
@IBAction func buttonPressed(_ sender: UIButton) {
count.add(num: sender.tag)
updateNumLabel()
}
...
class Count {
var num: Int = 0
func add(value: Int) {
num += value
}
}
こんなのを作ってみよう。
また、Countをデータであると捉えれば、CountとCountManagerを用意してあげるのもアリかなって思いますね。(問題文とは異なることになってしまいますが…)
class Count {
var num: Int = 0
}
struct CountManager {
static let shared = CountManager()
func plus(_ count: Count) {
count.num += 1
}
func minus(_ count: Count) {
count.num -= 1
}
}
など。
カウントアプリ - delegate -
なんだかよくわからないdelegate。TableViewとかでよく出てくるけど結局なんなの?ってのを、一旦いじってみてその裏側を知ってみようって感じです。あと自分でUIViewとか作った時にも綺麗なコード書く足がかりになってくれたりしないかなって感じです。
class ViewController: UIViewController {
let countManager = CountManager()
@IBOutlet weak var label: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
setupCountManager()
updateLabel()
}
func setupCountManager() {
countManager.delegate = self
}
@IBAction func plusButtonTapped(_ sender: Any) {
countManager.plus()
updateLabel()
}
@IBAction func minusButtonTapped(_ sender: Any) {
countManager.minus()
updateLabel()
}
func updateLabel() {
label.text = String(countManager.num)
}
}
extension ViewController: CountManagerDelegate {
func plus() {
countManager.num += 1
}
func minus() {
countManager.num -= 1
}
}
delegateの時とかextension使って意義を分離するの大事ですね。
class CountManager {
var delegate: CountManagerDelegate? = nil
var num: Int = 0
func plus() {
if let delegate = self.delegate {
delegate.plus()
}
}
func minus() {
if let delegate = self.delegate {
delegate.minus()
}
}
}
protocol CountManagerDelegate {
func plus()
func minus()
}
Managerで実装されている関数の中身をextensionで実装できるね!みたいな感じに思っていただければ。クソむずい。
カウントアプリ - 継承 -
var num: Int = 0
@IBOutlet weak var label: CountLabel!
override func viewDidLoad() {
super.viewDidLoad()
}
@IBAction func buttonTapped(_ sender: UIButton) {
if sender.tag == 1 {
label.plus()
}else {
label.minus()
}
}
CountLabelって誰だ!?って話ですね。UILabelを継承したCountLabelを構築してみましょう。この辺りは相当実用的かも。
import UIKit
class CountLabel: UILabel {
var num: Int = 0
func plus() {
num += 1
}
func minus() {
num -= 1
}
}
(まあこういった類の関数を持たせることはあんまり無いかもしれませんが…)
さてこれ以降は作るの大変そうで作ってないけどこんなのもいいんじゃないかなってのをまとめてみます
- tableView(あるいはcollectionView)の各セルにカウントが表示される、それらを現在の数、生成順序などでソートできる
- 電卓をMVCで作る(演算をenumで実装、などもいいですね)
- +ボタンと-ボタンの役割を他の機能に譲渡(スライドや加速度センサーなど)
- Firebaseと連携し、そのアプリを使っている誰でもリアルタイムにカウントを+-できる(RealtimeDatabaseを監視すればいけそう)
- ゲーム形式のものを作る(教科書の「先に○回押すゲーム」、カラーピッカーを応用して「表示色のRGBを当てるゲーム」、計算ゲームなどなど)
など!カウントアプリという特徴がほとんどないものだからこそ、そこに特徴を付け足すことで様々な世界を学ぶ手段を構築することが可能だと考えています。
さいごに
カウントアプリってほんといい塩梅の存在なので初心者にやらせるといい勉強になるんですが、対してそれだけやってると丸暗記が起こってあんまりいい効果をもたらさないって思ってます。こういった見た目の変化や内部構造の変化をもたせることで、知識を応用する練習になればいいなと思っています。この問題たちも作ってきた問題の一部くらいなので、今後も色々カウントアプリ派生の問題作っていきたいですね。
さてさて明日は、iPhoneメンターおいしんがベジェ曲線の書き方についての記事を投稿してくれるみたいです…!!!ベジェ曲線っていえば定義式がかなり複雑で、組み合わせ数の計算とか大変な気がしますね!楽しみにしています!