20201122のSwiftに関する記事は7件です。

電卓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)

こんな感じに書いたら%の計算をしてくれる様になりました。

とりあえず今回は計算方法のみ、に焦点をしぼってみました。

最後に

プログラミングは理解すれば楽しいけど、理解するまでが時間かかりますね。。
精進あるのみだと再度確認できました!

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

no such module 'Firebase' 原因のかもしれないこと

no such module 'Firebase' 原因の一つの例

僕の場合、このエラーの原因はxcodeのbundleIdentifierを変えたのでFirebaseに登録したbundleIdentifierと異なりエラーが起こりました。

解決方法

  • bundleIdentifierを元のものに戻す
  • Firebaseを作り直す

bundleIdentifierを元に戻すのはわかると思うので
Firebaseの作り直しで気をつけるところをわかりやすく説明しようと思います。

demo

このIOSという丸いボタンを押します。
そうすると、

demo

このような画面になるので、新しくしたbundleIdentifierをここに入力してfirebaseを作ればエラーが解決されると思います!!

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

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 ControllerModel 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.swift
import 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.swift
import 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.swift
import 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まで接続すれば良い感じでした。

Lesson1.5 Storyboard操作説明.001.jpeg

Lesson 1.4: Scroll Views

Scrolling Form

AutoLayoutの設定が難しいですね。自分は5、6回constrainsを全部消してやりなおしました。また、折角なのでiOS各端末で幅を綺麗に表示させたいと思ったのですが、そこが結構大変でした。下のように、Stack Viewの幅を親のScroll Viewのwidthと同じにすれば、各端末で同じにできるようです。

Lesson 1.4.001.png

実験

画像をズームできるようにするものでした。

ViewController.swift
import 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
    }
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

プログラミング歴三ヶ月 落とし物アプリLostを作った話

落とし物アプリLost

https://github.com/kai-nakao/Lost

ソースコードを全てGithubに載せています。よかったらみてください。

どうして作ろうと思ったか

  • 落とし物アプリがなかったから
  • 落とし物を位置情報に基づいて探せる社会にしたかったから。
  • 一人一人が落とし物を投稿することによって誰かが誰かの役に立つようにデザインするため

機能一覧

  • 位置情報に基づいた投稿
  • 位置情報に基づいた検索
  • 受け取り完了ボタンによりデータ削除
  • 位置情報取得

使ったライブラリ/フレームワーク一覧


こだわったポイント


  • FloatingPanelの採用
  • FloatingPanelの表示具合

落とし物を探す上で、写真と落とし物の特徴でまず見つけてから落とし物の詳細を見たいと思うので、初めはtipで写真と特徴だけを表示させてfullで落とし物の情報詳細まで見れるようにしました。
- FloatingPanelにピンとの相互性を持たせてピンの大きさの変化

demo

  • 投稿する時にカメラで撮るのかライブラリから選ぶのかの選択肢をalertActionで表示

demo

# 投稿の仕方

トップ画面でカメラボタンを押します。
次の画面で落とし物を見つけた場所を長押しします。
そうするとalertActionが出るのでどちらか(カメラで撮る or ライブラリ)を選び投稿画面に細かい詳細を入力して投稿ボタンを押すと投稿完了です。

demo

苦労したポイント


  • FloatingPanelを実装したこと
     FloatingPanelは全然記事がなく、公式の英語の記事を頑張って読んで試行錯誤しながら実装していきました。
    参考にしたものを共有します。

    https://github.com/SCENEE/FloatingPanel

  • FloatingPanelとピンの大きさに相互性を持たせること
    特にその中でも苦労したのはFloatingPanelをスワイプして消した時に選択中だったピンの大きさを元に戻すことです。
    そのソースコードを共有します。

SearchViewController.swift
func floatingPanelWillRemove(_ fpc: FloatingPanelController) {
        selected_marker.icon = self.imageWithImage(image: UIImage(named: "pin")!, scaledToSize: CGSize(width: 32.0, height: 37.0))
}

初めはfloatingPanelDidRemoveメソッドを使っていたのですが、これではそのメソッドが呼ばれるスピードが遅くなってしまいました。その問題の解決のために英語の公式の記事を読んでその中で近いものを試し、やっとの思いでこのメソッドにたどり着きました。
これが一番苦労したポイントです。

ユーザ目線で少し意識したこと


位置情報系アプリは上部にボタンがあるものもあるのですが、ユーザビリティを考えてボタンを下部に配置しました。

作ってみての感想

大変なこともありましたが、調べながら実装していくとどんどんできることが広がって楽しかったです。これからもいろいろなアプリを作っていきたいと思っています。

次に作るかもしれないアプリ→EATOUT

奢りか割り勘かを決めてからご飯に誘うアプリを作ろうと今思っています。作る目的は日本人特有の会計時のいらない駆け引きをなくすこととご飯に誘うアプリがあるとより楽にご飯に誘えるのではないかと思ったからです。
また面白いアプリをどんどん作っていきます!みてくださった皆様ありがとうございました!今後ともよろしくお願いします!

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

構造体について【Swift】

構造体とは?

  • 変数、定数、関数を1つのグループとしてまとめる仕組み。
  • 構造体の中で作られた変数・定数を プロパティ 、関数を メソッド という。
  • 構造体の中で作られた変数・定数を初期化することを イニシャライズ という。
  • 構造体を利用するにはインスタンス化が必要。
  • データ型は全て構造体である。( IntFloatDoubleStringBool

実装例

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 変数名 = 構造体名()
変数名.プロパティ名
変数名.メソッド名

参考

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

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の後ろに日付を付け方の画像を貼っておきます。
demo

このようにすると重複がなくなり、エラーが解消されると思います!

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

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などで使用する際などにサイズを固定化する必要がなくなります。

サンプルコード

gist

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()
        }
    }
}

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