作ったアプリ。ドットエディター16
こんにちは。皆様は普段どんなアプリを作っていますか?
ゲームは作っていますか?
ゲームを作るとしたら、私みたいな個人開発者だったら、めちゃめちゃ凝ったグラフィックとかは結構ハードル高いかと思います。
だったら逆にレトロな感じで、初代ファミコンのようなグラフィックやボタンデザインの方が、中途半端にならずにオシャレな感じになるのではないでしょうか。
今回はそんな、UIやスプライトに使う画像を、外出先や電車の中でポチポチ描けるドット絵エディターを作りました!
iOSアプリです。よかったらダウンロードしてください!
apps.apple.com16マス×16マスに限定したニッチなアプリです。理由はそれ以上細かい場合はPCでやった方が効率良いと思ったからです。
操作方法は、スクリーンショットを見ればなんとなくわかるかと思います。
ダウンロードしたら一つだけ注意していただきたいのが、「音が鳴る」ことです。
昔のスーファミのマリオペイントがすごく好きだったんですが、あれも音が鳴るんですよね。ドットを打つ時にぷちょぷちょ言ってるのが気持ちよかったんで、音が鳴るようにしました。
ツールバー下段がわかりにくいかと思うので説明します。
下段左から、直線(割愛)・ずらす・挿入・破棄(割愛)・スナップショット・ストック
ずらす・・・絵全体を上下左右に好きなように動かします。後で「もうちょい右だったぁ」というのが無くなります。
挿入・・・タップ位置で上下・左右に分断し、一方をずらします。「もうちょい離したかったぁ」という時に便利です。
スナップショット・・・絵を保存します。PNGに書き出すのではなく、アプリに保存します。ストックからいつでも呼び出せます。スナップショットを撮る時のアニメーションにはこだわったので、ぜひ見ていただきたい!笑。
ストック・・・スナップショットした絵を呼び出します。この時、上に重ねて描画されます。これにより、レイヤーのように使っていただけます。例えばボタンの枠だけ作って、重ねる画像は別に作り、書き出す時に重ねて表示するとか。ストックから消す場合は長押しです。
学習コストほぼ0で、スプライトの絵やボタンのイメージが簡単に製作できます!
音が鳴るので、マナーモード注意!
楽しいゲームを作るのなら、楽しみながら作った方がより面白いのができるかと思います♪☆
【Swift4】ToDoリストアプリを作る(4)
新規タスクを追加するところまで実装できました。
セルのテキストビューをタップするとテキストの編集はできますが、キーボードが閉じないので、編集が終わったらキーボードを閉じる処理をまずは実装します。
ソフトキーボードの「改行」表示を「Done」に変更
ストーリーボード上でセルのTextViewを選択し、Text Input TraitsのReturn KeyをDoneに変更します。
キーボードの改行ボタンの表示がDoneに変更されました。
テキストの編集を逐一監視し、改行だったらキーボードを閉じる
class TableViewCell: UITableViewCell,UITextViewDelegate { // ←追加 // 略 func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool { if text == "\n" { textView.resignFirstResponder() // キーボードを閉じる return false } return true }
テキストの変更を監視し、改行キーが押されたらキーボードを閉じる処理をしています。
Taskの変更を保存する
セルの方でTaskのインスタンスを保持し、テキストの編集やチェックボックスの変更があった時にインスタンスに変更を反映させます。その最新状態のインスタンスをTableViewControllerへ渡し、CoreDataに保存するという流れです。
import UIKit // セルの変更があった時にテーブルビューへ伝えるメソッド protocol CellDelegate { func didTaskChenge(task:Task,isTextChanged:Bool) } class TableViewCell: UITableViewCell,UITextViewDelegate { @IBOutlet weak var checkBox: CheckBox! @IBOutlet weak var textView: UITextView! var task:Task! // Taskのインスタンスを保持 var delegate:CellDelegate! // デリゲート override func awakeFromNib() { super.awakeFromNib() textView.delegate = self } // TableViewから実行する初期化のためのメソッドを定義 func dataInit( _ task:Task){ self.task = task checkBox.setChecked(task.isChecked) textView.text = task.text } // テキストビューのデータソース func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool { if text == "\n" { textView.resignFirstResponder() //キーボードを閉じる return false } return true } // 編集が終了したタイミング func textViewDidEndEditing(_ textView: UITextView) { print("エディット終了") task.text = textView.text // インスタンスに変更を加えて delegate.didTaskChenge(task: task, isTextChanged: true) // TableViewControllerに送る } // チェックボックスのタップイベント @IBAction func checkBoxTap(_ sender: CheckBox) { task.isChecked = !task.isChecked sender.setChecked(task.isChecked) // 変更して delegate.didTaskChenge(task: task, isTextChanged: false) // TableViewControllerへ } override func setSelected(_ selected: Bool, animated: Bool) { super.setSelected(selected, animated: animated) } }
TableViewController側のセルの初期化処理
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) as! TableViewCell let task = tasks[indexPath.row] cell.dataInit(task) cell.delegate = self return cell }
cell.dataInit(task)を実行することで、テーブルビュー側では初期化処理をせず、セル側で行うようにします。
TableViewでセルの変更を受け取る
デリゲートを追加してコールバックを受け取り、セーブする処理を実装します。
class TableViewController: UITableViewController,CellDelegate{ // ←追加 // セルからのコールバック func didTaskChenge(task: Task, isTextChanged: Bool) { if let index = tasks.index(where: {$0.dateUTC == task.dateUTC}){ tasks[index] = task saveData() } }
tasks配列の中から、dateUTCをIDがわりに当該Taskのインデックスを取得して、セルから送られてきた最新状態のTaskを格納し、saveData()でCoreDataに保存しています。
【Swift4】ToDoリストアプリを作る(3)
肝心要のCoreDataの設定
プロジェクトファイル内に自動的に作られた、アプリ名.Xdatamodeldから
Add Entityをクリックします。
追加されたEntityの名前を、今回は「Task」とします。
Attributesの「+」をクリックして、変数名とデータ型を以下のようにします。

text・・・タスクのテキスト
position・・・タスクのポジション(上から何番目かを表す)
isChecked・・・チェックマークにチェック済みかどうか
dateUIC・・・タスクを識別するためのIDの代わり。現在時刻を記憶する。
空のタスクを、テーブルビューの最下位置に追加する
TableViewControllerに戻り、インポートと、変数の定義と、データベースからTaskの配列を取ってくるメソッドと、まっさらなタスクを追加するメソッドを記述します。
長いです。
import UIKit import CoreData // ←追加 // 略 // タスクを保存する変数 var tasks:[Task] = [] var context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext override func viewDidLoad() { super.viewDidLoad() getData() tableView.reloadData() } // CoreDataからTaskを取得し、tasksに格納・position順に並び替えを行う func getData() { do { // CoreDataからデータをfetchしてtasksに格納 let fetchRequest: NSFetchRequest<Task> = Task.fetchRequest() tasks = try context.fetch(fetchRequest) tasks.sort(by: {$0.position < $1.position}) saveData() } catch { print("Fetching Failed.") } } // Taskを追加したり変更した時に呼ぶ func saveData(){ (UIApplication.shared.delegate as! AppDelegate).saveContext() } // TableViewの、Rowの数を返すメソッドではtasksの要素数を返す override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { // #warning Incomplete implementation, return the number of rows return tasks.count } // 空のタスクを新規追加するメソッド func addNewStickyNote(){ let task = Task(context: context) task.dateUTC = Date().timeIntervalSince1970 task.position = Int64(tasks.count) task.text = "" task.isChecked = false saveData() // tasksに追加し、TableViewに反映させる。追加した付箋までスクロールする tasks.append(task) tableView.insertRows(at: [IndexPath(row: tasks.count - 1, section: 0)], with: .top) tableView.scrollToRow(at: IndexPath(row: tasks.count - 1, section: 0), at: .bottom, animated: true) }
これで、addNewStickyNote()を呼び出せば、空のタスクが追加されるはずです。
前回作成したフローティングアクションボタンから呼び出してみましょう。
ViewControllerから呼び出すときは、
var tableViewController:TableViewController!
以前に作った変数から呼び出すことができます。
【Swift4】ToDoリストアプリを作る(2)
タスクの新規追加をするためにどのようなUIにするか、ToDoリストアプリにおいて悩みどころです。
今回は、Androidのフローティングアクションボタン風のボタンを右下に配置することにしました。
スマホの操作をしていると、「指を上の方に持っていくのは少々ストレスがかかる」と思い、指の移動を抑えた操作にします。
そう考えるとAndroidのUIは理にかなっていると、今更ながら思いました。
ボタンの配置
ViewControllerの右下にViewを配置し、その中にUIButtonを配置します。
ボタンがContainerViewの下に隠れないようにします。
UIデザインは自由にしましょう。
TableViewのスクロールイベントを拾う
タスクがボタンに隠れないよう、TableViewをスクロールしているときはボタンを隠すようにします。
メルカリの「出品」ボタンの動きを参考にしました。
TableViewControllerに以下を追加するだけで、スクロールイベントを拾うことができます。
override func scrollViewWillBeginDragging(_ scrollView: UIScrollView) { print("スクロール開始") } override func scrollViewDidEndDecelerating(_ scrollView: UIScrollView){ print("スクロール終了") }
ボタンを下方向の画面外に隠す・表示するアニメーション
ViewControllerにボタンをアウトレット接続します。
変数名はfloatingActionButtonにしてしまいました。笑
@IBOutlet weak var floatingActionButton: UIButton!
// アニメーションのコード
func showActionButton(){ floatingActionButton.isHidden = false floatingActionButton.center.y += 100 UIView.animate(withDuration: 0.2, delay: 0.2, options: [.curveEaseOut], animations: { self.floatingActionButton.center.y -= 100 },completion: nil) } func hideActionButton(){ UIView.animate(withDuration: 0.2, delay: 0.0, options: [.curveEaseIn], animations: { self.floatingActionButton.center.y += 100.0 }) { _ in self.floatingActionButton.center.y -= 100.0 self.floatingActionButton.isHidden = true } }
スクロール開始でボタンを隠す、終了時に表示
TableViewControllerのスクロールイベントに以下を追加します。
override func scrollViewWillBeginDragging(_ scrollView: UIScrollView) { print("スクロール開始") (parent as! ViewController).hideActionButton() } override func scrollViewDidEndDecelerating(_ scrollView: UIScrollView){ print("スクロール終了") (parent as! ViewController).showActionButton() }
ボタンを表示する際に、わずかにディレイをかけています。
念のために、ボタンが隠れるまでにかかる時間と同じだけの時間を空けて、意図しない動作を防ぐためと、アニメーションのタイミング的に心地良いと、個人的に感じたからです。
問題なく動作しましたか?
せっかくなのでFABっぽく影とかをつけてみる
extension UIView { func makeUp(){ self.layer.cornerRadius = 15 self.layer.shadowColor = UIColor.black.cgColor self.layer.shadowOffset = CGSize(width: 4, height: 4) self.layer.shadowOpacity = 0.4 self.layer.shadowRadius = 4 } }
対象のViewに上記のメソッドを実行し、ボタンや付箋に角丸やシャドウを付加すると、なんとなくそれっぽくなりました。
【Swift4】ToDoリストアプリを作る。

こんな感じのTODOリストを作ってみます。
ポイントは
・コアデータを利用してタスク情報を永続化
・テーブルビューをContainerViewに埋め込む形で利用
です。
何回かに分けてご紹介します。コードについては、拙い部分や、もっとこうした方がスッキリするよ!といったところがあるかもしれません。その際は教えていただけると助かります。
UIの作成に際してのオートレイアウトの使い方については割愛します。
プロジェクト作成
新規プロジェクト作成の際に
Use Core Data
にチェックを入れます(デフォルトでチェック入っている)。
これでCoreDataを簡単に利用できるようになります。
TableViewControllerをContainerViewに埋め込む

ストーリーボード上で、メインのViewController(以下ViewController)にContainerViewを画面いっぱいに配置します。
自動的に作られるViewControllerを選択して、deleteを押して削除します(ストーリーボード上から消え去ります)。
ストーリーボード上にTableViewControllerを配置。
ContainerViewから、controlキーを押しながらTableViewControllerまでドラッグし、ポップアップからEmbedをクリックします。
ContainerViewにTableViewControllerを含めることができました。
TableViewではなくTableViewControllerを使うのは、大きな利点があります。それは、セルに後ほどTextViewを配置しますが、これのテキストを編集しようとタップすると、TextViewがキーボードに隠れてしまうことがあります。TableViewControllerであれば、TextViewが隠れないように自動的にスクロールしてくれます。
TableViewControllerのセルのデザイン
ToDoリストっぽい、チェックボックスとテキストのデザインを作ります。
SwiftにはチェックボックスのUI部品がないので、まずはこれを作ります。
チェックボックス用のチェック済み、未チェックの画像を用意し、Assetsフォルダに投げ込みます。
名前はそれぞれ、checkedとuncheckedにしました。
New File…から、Cocoa Touch Classを選び、UIButtonを継承したCheckBoxというファイルを作ります。
import UIKit class CheckBox: UIButton { // Images let checkedImage = UIImage(named: "checked")! as UIImage let uncheckedImage = UIImage(named: "unchecked")! as UIImage // Bool property var isChecked: Bool = false { didSet{ if isChecked == true { self.setImage(checkedImage, for: UIControl.State.normal) } else { self.setImage(uncheckedImage, for: UIControl.State.normal) } } } override func awakeFromNib() { self.addTarget(self, action:#selector(buttonClicked(sender:)), for: UIControl.Event.touchUpInside) self.isChecked = false } @objc func buttonClicked(sender: UIButton) { } func setChecked(_ check : Bool){ isChecked = check } }
ボタンを押した時にチェックマークを反転させるようにはしません。
PrototypeCellsにCheckBoxと、TextViewを配置する
CheckBoxを置くには、UIButtonを配置してから、クラスをCheckBoxに変更します。
ここら辺は、自由にレイアウトしてください!
テーブルビュー情報
UITableViewControllerを継承した、TableViewControllerというファイルを作ります。
ストーリーボード上のUITableViewControllerのクラスをTableViewControllerに変更します。
ViewControllerから、TableViewControllerのメソッドを呼べるように、TableViewControllerの参照を保持しておきます。
import UIKit class ViewController: UIViewController { var tableViewController:TableViewController! override func viewDidLoad() { super.viewDidLoad() } override func prepare(for segue: UIStoryboardSegue, sender: Any?) { if let tableViewController = segue.destination as? TableViewController { self.tableViewController = tableViewController } } }
カスタムセルを定義する
UITableViewCellを継承したTableViewCellというファイルを作ります。
ストーリーボード上のPrototype CellのクラスをTableViewCellにし、Identifierを「Cell」にします。
TableViewCellに先ほど配置したチェックボックスとテキストビューを、アウトレット接続します。
@IBOutlet weak var checkBox: CheckBox!
@IBOutlet weak var textView: UITextView!
TableViewControllerに戻り、以下を追加します。
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) as! TableViewCell // Configure the cell... return cell }
withIdentifier: “Cell”にし、as! TableViewCellを付け加えています。
一息ついて、ひとまず動作確認
override func numberOfSections(in tableView: UITableView) -> Int { return 1 } override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { // 後で直してください return 1 } override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) as! TableViewCell cell.textView.text = “適当に長い文章を入れてみます” // Configure the cell... return cell }
正しく表示されましたか?
TextViewで長文を表示する際にスクロールさせないようにするには、プロパティのScrolling Enabledのチェックを外します。スクロールバーが非表示になり、全ての文章が表示されるようになります。
CoreDataを使う
CoreDataを使う
プロジェクト作成時に、use CoreDataにチェックを入れる。
プロジェクト名.xcDatamodeldから、Add Entitiesをクリック。
ENTITIESのモデル名を定義する(今回はDataとしています)
Attributesに、利用するデータ型を定義する。
import CoreData
インポートします。
var data:[Data] = [] func getData() { let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext do { let fetchRequest: NSFetchRequest<Data> = Data.fetchRequest() data = try context.fetch(fetchRequest) } catch { print("Fetching Failed.") } }
dataにData型の配列が入ります。
保存する。
let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext let data = Data(context: context) data.propaties = “value” // プロパティに値を入れたり (UIApplication.shared.delegate as! AppDelegate).saveContext()
削除する。
let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext context.delete(data[0]) // 削除するオブジェクト UIApplication.shared.delegate as! AppDelegate).saveContext()
【part5】初めてのヒッチハイク旅!!
国道20号線沿い、信号を渡ると”瑰泉”というポイントで降ろしてもらい、私のヒッチハイクは終わりました。
今の時刻は午前4時半。寒川からここ石和温泉まで約15時間の旅。瑰泉の建物の光が夜空を明るくしています。
www.yu-kaisen.jp
ゴールに到達
スタートした時は先が見えなかったヒッチハイク旅も、何台もの車に乗せてもらい、移動距離が増えるほど、点が線になって行くように感じました。少し大げさなようですが、旅が進むほど不安が減少し自信がついてきます。苦境に陥っても「必ずたどり着く」という気持ちになります。というより「まぁ、ゴールできるっしょ!」という感じの方が近いかな笑。
瑰泉は24時間営業で、雑魚寝スペースがあります。これは、2台目のトラックの運転手さんに教えていただきました。山梨に着けば、ビジネスホテルかカプセルホテル、漫画喫茶、カラオケの一つくらいはあるだろうと思っていたのは余りにも浅はかでした。先のアドバイスが無ければ野宿をしていたかもしれません。ちゃんとした寝床にありつけたのは本当に有り難いことでした。
雑魚寝スペースに転がり落ちるようにして横になりました。やや硬いマットレスと枕も十分な快適さです。掛け布団はなさそうなので、タオルと上着をかけます。前日のスタート時点から今までが鮮明に思い出されますが、15時間しか経っていない事に逆に驚きました。濃密です。出会った人々全員に感謝しながら寝たのは、決して大げさに言っていません。翌日は施設の温泉に入り、朝食を食べ、ハンモックに揺られながら(今頃他のスタッフたちは、ハワイでハンモックに揺られているのだろうか)、今日1日の予定を考えました。あとは、普通の観光をしました!
ヒッチハイクで得たもの
半ば勢いだけで決行したヒッチハイクは、無事に終わりました。様々なブロガーがヒッチハイクについての記事を書かれています。自分も読ませていただきました。中にはヒッチハイクで日本一周された方もいらっしゃるので、ヒッチハイクのやり方だったり注意点は、そのような上級者の方の意見が参考になると思います。ここでは、あくまで私なりに感じた事を書いていきます。
- 困難な状況を打開する力がつく
深夜のサービスエリアに降り立った時、一瞬途方に暮れましたが状況を検証し「今の状況は”悪い”。どうにかしなければ」という思いが立ち、すぐに行動に移りました。車でしか来ない場所に、生身で居る。この苦境から逸脱するには?結果、片っ端から声をかけるという方法を取りましたが(笑)、日常生活や仕事で困難なときに、それを乗り越える力がつくと思います。
- 人や、様々な事象に感謝の気持ちを持てる
人々に対しては言わずもがなですが、インフラが整っているという事に有り難みを感じました。車の税金や有料道路について煩わしいと思っても、道が続いていない事には移動もできません。旅の趣旨ではないですが、山梨に行きたいのなら電車を利用して3時間ほどで着いてしまいます。震災などで停電になった時に電気の有り難みを感じますが普段はさほど感じないように、当たり前なことが当たり前ではないのだと気付かされました。
- 様々な職の人と出会える
普段関わりを持たない職種の人と話すことができます。ヒッチハイク関連の記事でよく言われていますね。私は美容師なので様々な方とお話しをしますが、ヒッチハイクの場合は出会いの状況が特殊なぶん、不思議な空気感の中でする会話は面白いものです。
- 目的達成までの一本道を進んだという成功体験ができる
自分で目的地を決め、それまでの道のりを考える。全ての意思決定を自分でする。その結果は自分に降りかかる。他の誰の責任でもない。目的地に着いた時、意思決定をした点が線になる。人生でそのような体験はなかなか容易ではないですが、ヒッチハイクなら短時間に凝縮してそのような成功体験が得られると思います。人間の脳はイメージと実際に体験したこととの区別ができないそうです。これはスポーツにおいて大切な事ですが、一連のプロセスは様々なところで役立つのではないでしょうか。じゃあヒッチハイクもイメトレでいいじゃんというとそうではなく、あなたが今後達成したい目標への道のりを、より明確にイメージできるのでは?という事です。
- メンタルが鍛えられる
これもよく言われますね。私も高校生に後ろ指を指されたり、車の中から笑われたり、睨まれたりとメンタルを削られる場面は多々ありました。ですが、窮地に追い込まれるとメンタルどうのこうの言っている場合じゃ無くなります。そのうち、そのようなネガティブな反応なんて、旅で得られるものの価値に比べたら小っぽけに感じるようになります。
- 話のネタになる
私は休み明けからお客様と会話するときは、ほぼほぼ今回の旅の話をしています。かなりの確率で「すごい!」と言われます。「ハワイ行くより良い経験ができたんじゃない?」とさえ言われる事もあります。そもそものきっかけが、みんながハワイ行ったら帰国後はその話題ばかりになる、自分も何かネタを作らなきゃ、というものでした笑。
ヒッチハイクの危険性
メリットばかりを挙げましたが、危険性も伴うのは事実です。全くの見ず知らずの人の車に乗り込むわけですから、100%安全の保証はないのです。心の優しい人だけがふるいにかけられて止まっているとは限りません。あなたが腕っ節が強いからと言っても然りです。車内は半密室だということは絶対に忘れてはなりません。逆にヒッチハイカーを乗せる場合もです。カージャックというと非現実的に聞こえますが、乗せる際は手荷物検査くらいはしても良いと思います。
ヒッチハイクをしようかな〜と思っている方へ
決められたルートをトレースする旅行ではなく、自分の判断によって、吉と出るか凶と出るかわからないが自分を信じて続いてゆく旅は、私にとって価値のあるものとなりました。3時間もあれば着く距離を、15時間かけてでも行った事に意味があります。その15時間は驚くほどあっという間で、ぎゅっと凝縮されています。宝物をたくさん発見できた旅でした。
それでもヒッチハイクを人に勧めにくいのは、やはり危険性が伴うという事でしょう。何かを得るためにリスクを背負うのは悪いことではないですが、ヒッチハイクのリスクは、身の危険です。私はたまたま良い人に巡り会えただけかもしれません。
ですので、ヒッチハイクはオススメしません笑
成功体験を積むには、いくらでも方法はあります。たとえば歌手を目指しているとして度胸をつけたい場合、ヒッチハイクよりも路上で歌った方が得られる経験値が高そうです。
今回乗せてくださった方には最高の感謝の意を表明します。その上で、次のことを述べます。
車に乗せる事だけが、人の優しさの本質ではありません。車を持っていない人だって、胸いっぱいに染み渡る優しさを持っている人もいるはずです。人の優しさに触れる機会として見た場合、ヒッチハイクが最善の方法とは限りません。表に出ていないだけで、見返りを期待したり、下心を潜めている場合も無いとは言い切れません。
ただ、海老名で立っていた時にスポーツドリンクを差し入れて下さった方は、素で優しい方だと思いました。
以上が、私の見解です。
ヒッチハイクに挑戦される方は、くれぐれも怪我のないようにお気をつけください!
さいごに
今回の私の身勝手な旅の道中、関わった全ての方に心から感謝しています。
最後までお読みいただきありがとうございました。