- 投稿日:2020-11-22T21:50:12+09:00
電卓app 〜%計算〜
はじめに
最近は便利なものでYoutubeで探せばアプリ開発を1〜10まで動画で説明してくれる動画が見つかります。
全くSwiftを触ったことがない状態で電卓アプリを1から作成したときに立ちはだかった壁についてご紹介します。
今回は壁の中でも%計算方法について書きたいと思います。
壁その1
まず立ちはだかったのはどこを探しても%計算のコードが見当たらない!!!
私がやってほしかった計算は、1000円のものの30%引きはいくら?と計算してくれる%計算。
検索してもしてもこうやってね、が出てこずに途方に暮れていました。。。
コードはどこか探せばあって全てコピペするものだ、という勘違いをしていたこともいけませんでした。
%計算ってどうやるの??
1000円の商品が10%引きになってました。さて、いくらになるでしょう、が%の問題です。
1000ー(1000円の10%オフ)=支払い金額
ということは、1000円の10%オフという計算を覚えさせれば良いということだ!!と分かりました。
%計算のコード
かくかくしかじか、コードに当してみると、
case "%": let huga:Double = Double(firstNumber) ?? 0 let huga1:Double = Double(secondNumber) ?? 0 let hoge:Double = 100 secondNumber = String(huga * huga1 / hoge)こんな感じに書いたら%の計算をしてくれる様になりました。
とりあえず今回は計算方法のみ、に焦点をしぼってみました。
最後に
プログラミングは理解すれば楽しいけど、理解するまでが時間かかりますね。。
精進あるのみだと再度確認できました!
- 投稿日:2020-11-22T14:56:44+09:00
no such module 'Firebase' 原因のかもしれないこと
no such module 'Firebase' 原因の一つの例
僕の場合、このエラーの原因はxcodeのbundleIdentifierを変えたのでFirebaseに登録したbundleIdentifierと異なりエラーが起こりました。
解決方法
- bundleIdentifierを元のものに戻す
- Firebaseを作り直す
bundleIdentifierを元に戻すのはわかると思うので
Firebaseの作り直しで気をつけるところをわかりやすく説明しようと思います。このIOSという丸いボタンを押します。
そうすると、このような画面になるので、新しくしたbundleIdentifierをここに入力してfirebaseを作ればエラーが解決されると思います!!
- 投稿日:2020-11-22T12:29:00+09:00
Develop in Swift Data CollectionsでiOS開発を学ぶ (2): Lesson 1.3からLesson1.4
https://qiita.com/mk2/items/6091f8eb195fa3237c4e の続き
Lesson 1.3: Model-View-Controller
iOS/Mac開発といえば、MVCだと個人的には思っています。(SwiftUIの登場で考え方も変わるのかもしれませんが…)
View Controller
、Model Controller
はなんとなく知っていたのですが、Helper Controller
という考え方があるのは知りませんでした。ただ、この説明を見る限り、ユーティリティ的な感じがしますね。“Helper Controllers
Helper controllers are useful anytime you want to consolidate related data or functionality so that it can be accessed by other objects in your app. One common example of a helper controller is a NetworkController, which manages all the network requests in a given app.”抜粋:: Apple Education “Develop in Swift Data Collections”。 Apple Inc. - Education、2020年 Apple Books https://books.apple.com/jp/book/develop-in-swift-data-collections/id1511183970
また、下記のようにソースコード、リソースをグルーピングするのが良いというのはiOS開発をがっつりやったことがないので、へーという感じでした。
“Many developers make groups for the following:
- View controllers
- Views
- Models
- Model controllers
- Other controllers
- Protocols
- Extensions
- Resources
- Storyboards
- Frameworks
”抜粋:: Apple Education “Develop in Swift Data Collections”。 Apple Inc. - Education、2020年 Apple Books https://books.apple.com/jp/book/develop-in-swift-data-collections/id1511183970
実験
今回は、好きなアスリートを記入できるアプリのようです。最終的に作成したコードを載せておきますね。
Athlete.swiftimport Foundation struct Athlete { var name: String var age: Int var league: String var team: String var description: String { return "\(name) is \(age) years old and plays for the \(team) in the \(league)." } }AthleteTableViewController.swiftimport UIKit class AthleteTableViewController: UITableViewController { struct PropertyKeys { static let athleteCell = "AthleteCell" } var athletes: [Athlete] = [] override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) tableView.reloadData() } // MARK: - Table view data source override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return athletes.count } override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCell(withIdentifier: PropertyKeys.athleteCell, for: indexPath) let athlete = athletes[indexPath.row] cell.textLabel?.text = athlete.name cell.detailTextLabel?.text = athlete.description return cell } @IBSegueAction func addAthlete(_ coder: NSCoder) -> AthleteFormViewController? { return AthleteFormViewController(coder: coder) } @IBSegueAction func editAthlete(_ coder: NSCoder, sender: Any?) -> AthleteFormViewController? { let athleteToEdit: Athlete? if let cell = sender as? UITableViewCell, let indexPath = tableView.indexPath(for: cell) { athleteToEdit = athletes[indexPath.row] } else { athleteToEdit = nil } return AthleteFormViewController(coder: coder, athlete: athleteToEdit) } @IBAction func backToTable(_ segue: UIStoryboardSegue) { guard let controller = segue.source as? AthleteFormViewController, let athlete = controller.athlete else { return } if let selectedIndexPath = tableView.indexPathForSelectedRow { athletes[selectedIndexPath.row] = athlete } else { athletes.append(athlete) } } }AthleteFormViewController.swiftimport UIKit class AthleteFormViewController: UIViewController { @IBOutlet weak var nameTextField: UITextField! @IBOutlet weak var ageTextField: UITextField! @IBOutlet weak var leagueTextField: UITextField! @IBOutlet weak var teamTextField: UITextField! var athlete: Athlete? required init?(coder: NSCoder) { self.athlete = nil super.init(coder: coder) } init?(coder: NSCoder, athlete: Athlete?) { self.athlete = athlete super.init(coder: coder) } override func viewDidLoad() { super.viewDidLoad() // Do any additional setup after loading the view. updateView() } func updateView() { nameTextField.text = athlete?.name if let age = athlete?.age { ageTextField.text = "\(age)" } leagueTextField.text = athlete?.league teamTextField.text = athlete?.team } /* // MARK: - Navigation // In a storyboard-based application, you will often want to do a little preparation before navigation override func prepare(for segue: UIStoryboardSegue, sender: Any?) { // Get the new view controller using segue.destination. // Pass the selected object to the new view controller. } */ @IBAction func save(_ sender: Any) { guard let name = nameTextField.text, let ageString = ageTextField.text, let age = Int(ageString), let league = leagueTextField.text, let team = teamTextField.text else { return } athlete = Athlete(name: name, age: age, league: league, team: team) performSegue(withIdentifier: "SaveAthlete", sender: self) } }わかりにくかった点
本の通りに進めていけば、多分だいたい完成させられると思うのですが、
Step 6 Perform the Unwind Segue in Storyboard
の節の下記の記述がよくわからずかなり四苦八苦しました。“Finally, you need to create the unwind segue. In the storyboard, Control-drag from the athlete form scene in the Document Outline to the view controller's Exit, then choose your unwind segue. Give this segue a name by selecting it in the Document Outline and adding the identifier in the Attributes inspector.”
抜粋:: Apple Education “Develop in Swift Data Collections”。 Apple Inc. - Education、2020年 Apple Books https://books.apple.com/jp/book/develop-in-swift-data-collections/id1511183970
正解は、下のようにAthele Form View ControllerからcontrolドラッグでExitまで接続すれば良い感じでした。
Lesson 1.4: Scroll Views
Scrolling Form
AutoLayoutの設定が難しいですね。自分は5、6回constrainsを全部消してやりなおしました。また、折角なのでiOS各端末で幅を綺麗に表示させたいと思ったのですが、そこが結構大変でした。下のように、Stack Viewの幅を親のScroll Viewのwidthと同じにすれば、各端末で同じにできるようです。
実験
画像をズームできるようにするものでした。
ViewController.swiftimport UIKit class ViewController: UIViewController, UIScrollViewDelegate { @IBOutlet weak var scrollView: UIScrollView! @IBOutlet weak var imageView: UIImageView! override func viewDidLoad() { super.viewDidLoad() // Do any additional setup after loading the view. scrollView.delegate = self } override func viewDidAppear(_ animated: Bool) { updateZoomFor(size: view.bounds.size) } func viewForZooming(in scrollView: UIScrollView) -> UIView? { return imageView } func updateZoomFor(size: CGSize) { let widthScale = size.width / imageView.bounds.width let heightScale = size.height / imageView.bounds.height let scale = min(widthScale, heightScale) scrollView.minimumZoomScale = scale scrollView.zoomScale = scale } }
- 投稿日:2020-11-22T01:54:23+09:00
プログラミング歴三ヶ月 落とし物アプリLostを作った話
落とし物アプリLost
ソースコードを全てGithubに載せています。よかったらみてください。
どうして作ろうと思ったか
- 落とし物アプリがなかったから
- 落とし物を位置情報に基づいて探せる社会にしたかったから。
- 一人一人が落とし物を投稿することによって誰かが誰かの役に立つようにデザインするため
機能一覧
- 位置情報に基づいた投稿
- 位置情報に基づいた検索
- 受け取り完了ボタンによりデータ削除
- 位置情報取得
使ったライブラリ/フレームワーク一覧
- Core Location(https://developer.apple.com/documentation/corelocation)
- Firestore(https://firebase.google.com/docs/firestore?hl=ja)
- Firestorage(https://firebase.google.com/docs/storage)
- GoogleMapsSDK(https://developers.google.com/maps/documentation/ios-sdk/overview)
- FloatingPanel(https://github.com/SCENEE/FloatingPanel)
こだわったポイント
- FloatingPanelの採用
- FloatingPanelの表示具合
落とし物を探す上で、写真と落とし物の特徴でまず見つけてから落とし物の詳細を見たいと思うので、初めはtipで写真と特徴だけを表示させてfullで落とし物の情報詳細まで見れるようにしました。
- FloatingPanelにピンとの相互性を持たせてピンの大きさの変化
- 投稿する時にカメラで撮るのかライブラリから選ぶのかの選択肢をalertActionで表示
# 投稿の仕方
トップ画面でカメラボタンを押します。
次の画面で落とし物を見つけた場所を長押しします。
そうするとalertActionが出るのでどちらか(カメラで撮る or ライブラリ)を選び投稿画面に細かい詳細を入力して投稿ボタンを押すと投稿完了です。苦労したポイント
FloatingPanelを実装したこと
FloatingPanelは全然記事がなく、公式の英語の記事を頑張って読んで試行錯誤しながら実装していきました。
参考にしたものを共有します。FloatingPanelとピンの大きさに相互性を持たせること
特にその中でも苦労したのはFloatingPanelをスワイプして消した時に選択中だったピンの大きさを元に戻すことです。
そのソースコードを共有します。SearchViewController.swiftfunc floatingPanelWillRemove(_ fpc: FloatingPanelController) { selected_marker.icon = self.imageWithImage(image: UIImage(named: "pin")!, scaledToSize: CGSize(width: 32.0, height: 37.0)) }初めはfloatingPanelDidRemoveメソッドを使っていたのですが、これではそのメソッドが呼ばれるスピードが遅くなってしまいました。その問題の解決のために英語の公式の記事を読んでその中で近いものを試し、やっとの思いでこのメソッドにたどり着きました。
これが一番苦労したポイントです。ユーザ目線で少し意識したこと
位置情報系アプリは上部にボタンがあるものもあるのですが、ユーザビリティを考えてボタンを下部に配置しました。
作ってみての感想
大変なこともありましたが、調べながら実装していくとどんどんできることが広がって楽しかったです。これからもいろいろなアプリを作っていきたいと思っています。
次に作るかもしれないアプリ→EATOUT
奢りか割り勘かを決めてからご飯に誘うアプリを作ろうと今思っています。作る目的は日本人特有の会計時のいらない駆け引きをなくすこととご飯に誘うアプリがあるとより楽にご飯に誘えるのではないかと思ったからです。
また面白いアプリをどんどん作っていきます!みてくださった皆様ありがとうございました!今後ともよろしくお願いします!
- 投稿日:2020-11-22T01:47:23+09:00
構造体について【Swift】
構造体とは?
- 変数、定数、関数を1つのグループとしてまとめる仕組み。
- 構造体の中で作られた変数・定数を プロパティ 、関数を メソッド という。
- 構造体の中で作られた変数・定数を初期化することを イニシャライズ という。
- 構造体を利用するにはインスタンス化が必要。
- データ型は全て構造体である。(
Int
、Float
、Double
、String
、Bool
)実装例
struct Human { // プロパティを定義 let name: String let age: Int // initを使用することでイニシャライザ定義 init(name: String, age: Int) { self.name = name self.age = age } func introduction() { print("私の名前は\(name)です。年齢は\(age)歳です。") } } // Human構造体をインスタンス化 let human = Human(name: "山田太郎", age: 25) // introductionメソッドへアクセス human.introduction() // 私の名前は山田太郎です。年齢は25歳です。定義方法
struct 構造体名 { // 構造体の定義 }プロパティとは?
インスタンスプロパティ
- 型のインスタンスに紐付くプロパティ
- 型のインスタンスに紐付くため、インスタンス化しなければ使うことができません。
スタティックプロパティ
- 型自身に紐付くプロパティ
- 型の性質を表すプロパティを定義したいとき(大規模なプロジェクト以外はあまり使う機会がない?)
- 静的という意味なので
let
を推奨インスタンスプロパティとスタティックプロパティの違い
struct Human { // スタティックプロパティ static let name = "山田太郎" // インスタンスプロパティ let age = 25 } // スタティックプロパティへアクセス Human.name // OK Human().name // NG // インスタンスプロパティへアクセス Human.age // NG Human().age // OK
型名().プロパティ名
(型名に()
をつけることでデフォルトのイニシャライザが働き、インスタンス化される)イニシャライザとは?
- プロパティを初期化する為のもの(他の言語ではコンストラクタとほぼ同様)
- インスタンス化時に実行される特殊なメソッド
書式
init(引数名: 型名, ...) { // selfは構造体自身のインスタンスを意味する self.プロパティ名 = 値(引数を使ったりする) }メンバーワイズイニシャライザ
- デフォルトで用意されている特殊なイニシャライザ
- イニシャライザは定義しない
- 単純な初期化ならメンバーワイズイニシャライザが使えるが、イニシャライザを定義すると使えなくなる
インスタンス化の方法
イニシャライザを定義していない時
let 変数名 = 構造体名()イニシャライザを定義した時
let 変数名 = 構造体名(引数名: 型名, ...)メンバーワイズイニシャライザを使用する時
let 変数名 = 構造体名(プロパティ名: 値, ...) // プロパティを直接指定するアクセス方法
let 変数名 = 構造体名() 変数名.プロパティ名 変数名.メソッド名参考
- 投稿日:2020-11-22T01:07:29+09:00
Failed to register bundle identifierの解決法について
Failed to register bundle identifier が出たときのエラーの原因
エラーの原因は、別の誰かが今使っている bundleidentifierのIDを
使用しているため、この Bundle Identifier を使えなかったというところにあります。どうして使われていると出るのか
- 使用していたAppleIDを変更した
- 誰かにプロジェクトを渡して、その人のmacでビルドして実機で実行してもらった
という可能性が高いです。
その場合、その別のAppleIDでBundle Identifierを使われてしまったため自分で使えなくなったということです。
ただ、このBundle Identifierの占有は1週間で期限が切れますので、その後はまた使えるようになります。解決方法
- 一週間待つ
- bundle Identifierの後ろに日付をつけてbundle Identifierの重複を防ぐ
bundleIdentifierの後ろに日付を付け方の画像を貼っておきます。
このようにすると重複がなくなり、エラーが解消されると思います!
- 投稿日:2020-11-22T00:58:30+09:00
Image+GeometryReaderを使ってAspectFill + clipToBoundsを再現する
https://medium.com/@wendyabrantes/swift-ui-square-image-modifier-3a4370ca561f
こちらの記事を参考に実装しました。GeometryReader(content: { geometry in Image(uiImage: image) .resizable() .scaledToFill() .frame(width: geometry.size.width, height: geometry.size.height) }) .aspectRatio(1, contentMode: .fit)上記の記事との違いはGeometryReader内のImageの属性に
.frame
でGeometryReaderのsizeを渡しています。Imageにframeを渡すことで、GeometryReaderの表示領域に対してscaledToFillのリサイズが当てられます。よってアスペクト比を固定しつつ中央寄せで任意のアスペクト比でクリップすることができるようになります。この方法のメリットとして、子要素でサイズが決まるのではなくサイズは親要素側で提供された表示可能領域にあわせてリサイズするのでLazyVGridなどで使用する際などにサイズを固定化する必要がなくなります。
サンプルコード
struct ProductGridCell: View { let name: String let price: Int let image: UIImage var body: some View { VStack(alignment: .leading, spacing: 3) { GeometryReader(content: { geometry in Image(uiImage: image) .resizable() .scaledToFill() .frame(width: geometry.size.width, height: geometry.size.height) }) .aspectRatio(1, contentMode: .fit) .clipShape(RoundedRectangle(cornerRadius: 4)) Text(name) .foregroundColor(.primary) .font(.caption) Text("¥\(price)") .font(.caption2) .foregroundColor(.secondary) } } } struct ProductGridCell_Previews: PreviewProvider { static var previews: some View { ScrollView { LazyVGrid(columns: [GridItem(), GridItem(), GridItem()], content: { ForEach(0..<20) { _ in ProductGridCell(name: "どら焼き", price: 300, image: UIImage(imageLiteralResourceName: "product")) } }) .padding() } } }