20210508のSwiftに関する記事は5件です。

TableViewの基本 備忘録

目的 TableViewを使用するときに毎回検索しているため、一通りの流れとしてメモを残す 環境 Xcode12.1 Swift5 流れ ・Main.storyboadの設定 ・TableViewのコードを記述 ・TableViewCellの作成 ・TableViewCellのコードを記述 ・Assetsに画像を設定 ・実行 Main.storyboadの設定 Main.storyboadにあるViewControllerにTableViewを設定します。 Xcodeの右上にある+ボタンからTableViewを検索し、ViewControllerにドラッグします。 右下のAdd New Constraintsから制約を設定します。 アシスタントエディタを開きます。 キーボードのをCmd、Opt、Ctr、Enterを同時に押し、アシスタントエディタを開きます。 stroyboadのTableViewをCtrを押しながら、エディタにドラッグし、outletを設定します。 変数名はtableViewとしました。 TableViewのコードを記述 以下のようにコードを記述します。 tableView(:numberOfRowsInSection:)は、セルの個数を設定し、 tableView(:cellForRowAt:)はセルの内容を設定します。 ViewController.swift import UIKit class ViewController: UIViewController { private let languageArray = ["Swift","Kotlin","AWS","HTML","CSS","JavaScript"] private let cellId = "cell" @IBOutlet weak var tableView: UITableView! override func viewDidLoad() { super.viewDidLoad() tableView.dataSource = self tableView.delegate = self let nib = UINib(nibName: "CustomViewCell", bundle: nil) tableView.register(nib, forCellReuseIdentifier: cellId) tableView.reloadData() } } extension ViewController:UITableViewDelegate,UITableViewDataSource{ func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return languageArray.count } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCell(withIdentifier: cellId, for: indexPath) as! CustomViewCell cell.setLayout(languageName: languageArray[indexPath.row]) return cell } func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { return 100 } } TableViewCellの作成 XcodeのナビゲーションエリアでCmd + Nを押します。 Cocoa Touch Class を選択しNext CalssにCustomViewCell SubClass ofにUITableViewCell Also create XIB fileにチェックをつけてNext 保存場所はそのままでCreate 生成されたCustomViewCell.xibを開き、ドラッグして大きさを調整します。 中にTextLabelやImageViewなどCellに配置する部品を配置し、コードとoutlet接続を行います。 TableViewCellのコードを記述 以下のようにコードを記述します。 setLayoutメソッドの中でセルのLabelとImageを設定します。 CustomViewCell.swift import UIKit class CustomViewCell: UITableViewCell { @IBOutlet weak var logoImage: UIImageView! @IBOutlet weak var languageName: UILabel! override func awakeFromNib() { super.awakeFromNib() logoImage.layer.cornerRadius = logoImage.frame.size.width / 2 logoImage.contentMode = .scaleAspectFill } override func setSelected(_ selected: Bool, animated: Bool) { super.setSelected(selected, animated: animated) } func setLayout(languageName:String){ self.languageName.text = languageName self.logoImage.image = UIImage(named: languageName) } } Assetsに画像を設定 Assetsに使用する画像を保存します。 実行 完成です。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

iOSアプリをデザインしよう!

title: iOSアプリをデザインしよう! tags: #swift #Xcode #デザイン author: sanrio_1122 slide: false はじめに 皆さんこんにちは。 この記事では、iOSアプリ開発におけるデザインについて紹介します。 基本的なアプリデザインの手順から、プラスアルファのtipsまで幅広く扱うので必要な箇所を読んで頂けたら嬉しいです☺️ 目次   1. デザインの流れ   2. 色を決める   3. ラフ画制作   4. Xcodeで実装   5. ロゴ作成   6. アイコン作成   7. 【+α】デザインtips デザインの流れ まずは、機能を実装し終わった後からリリースまでに必要な基本的なデザインの流れを掴みましょう。 基本的な流れは以下の通りです。 色を決める ↓ ラフ画でデザインや配色を決める ↓ 実装 ↓ ロゴ・タイトル制作 ↓ アイコン作成 それでは詳しく見ていきましょう。 色を決める まず初めに、アプリのテーマカラーを決めましょう。 使用する色は3色に絞るのがおすすめです。 この3色をメインカラー、ベースカラー、アクセントカラーと言いますが、「じゃあどうやって決めるの?」というギモンを解決していきましょう。 i.ユーザーの年代、性別から決める このアプリのターゲット(ユーザー)はどんな人ですか?年代と性別を絞るだけでも使用する色が絞られます。いわゆる「トーン」が決まっていくイメージです。 例)女子高校生向けアプリ→パステルカラーでかわいい系にしよう! 大学生向け飲み会ゲームアプリ→明るく楽しいイメージの配色にしよう! ii.アプリのテーマから決める 色には意味や色が与える印象があります。アプリのテーマに沿った色を選びましょう。 例)読書アプリ→緑色の持つイメージ「安心」「癒し」 iii.競合アプリをリサーチする 他の競合アプリはどんな色を使っているのか調べることで、テーマごとの大体の傾向がわかります。その中でも差別化を図るために、あまり使われていない色を3色の中に含ませる、グラデーションを使う、など工夫していきましょう。 ラフ画制作 使用する色が決まったらラフ画を描いてみましょう! 実装する前にざっと画面のデザインを書き起こしてみることで、足りない部分に気づけたり、過剰なデザインを削ぎ落とすことができます。 ・紙にStoryboardを書く ・どの色をどのパーツに配色するか ・ボタンの形や影はどうするか ・全体像を見て、そのデザインがユーザーやテーマに沿っているか などを確認しましょう。 ・デザインツール「Figma」を使う Figmaとは、ブラウザ上で簡単にデザインができるツールです。無料で使える本格的なデザインツールなので興味がある方は使ってみてください。 ちなみにGeekSalonのUIUXコースではこのFigmaを使ってデザインを学びます。 Xcodeで実装 ここからはラフ画をXcodeで再現していきます。 以下ではオススメのデザインライブラリや、少し手を加えるだけでアプリらしくなるものを載せました。 i.ライブラリ「IBAnimatable」を入れる ボタンを丸くする、パーツに影をつけるなど、本来コードで書かなくてはいけないデザインが、このライブラリを使えばStoryboard上で操作できます。 Github CocoaPodsの人はPodfileに以下を打ち込んでください。 pod 'IBAnimatable' 使い方 こちらの記事がわかりやすいです。 ざっくり言うと、パーツを選択した状態でカスタムクラスを”Animatable〜(パーツ名)”に変えると、右側のプロパティの機能が拡張されます。 ii.NavigationControllerとTabbarControllerの色を変更する NavigationControllerとTabbarControllerの色を変えると一気に本格的なアプリに見えます。 NavigationnBarを選択し、Bar Tintからカラーを変更することができます。 TabBarControllerも同様に、パーツ選択からBar Tintでカラー変更を行います。 ロゴ作成 ここからはタイトル画面やアイコンで使えるようなロゴを作っていきます。今回はフォントを入れることによりロゴを作ります。 Xcodeにデフォルトで入っているフォントはSystemというものです。 Tのアイコンをクリックするとフォントやスタイル、サイズのメニューが出てきます。 ここをSystemからCustomに変更すると、ズラーっとフォントが選べるようになります。 しかし日本語対応のフォントや、凝ったフォントは入っていません。そこで外部からフォントファイルをダウンロードしてXcodeに入れましょう。 1.Mac本体にフォントファイルを保存する 2.xcodeにフォントのファイルを入れる これでデフォルト以外のフォントが使えるようになり、個性的なロゴが作成できます。 オリジナルロゴを使って目を引くタイトル画面にしましょう。 アイコン作成 最後にアイコンを作っていきましょう。 Macにデフォルトで入っている「Keynote」を使います。 こちらの記事がおすすめです。 また、アイコンは大きく分けて2パターンあります。 それはロゴをアイコンにしているものとトレードマークをアイコンにしているものです。 アイコンはアプリの顔とも言えるので、シンプルかつ印象的なアイコンを作りましょう。 また、こちらの記事からアイコンデザインの流行が学べます。 【+α】デザインtips ・NavigationBarに画像を入れる NavigationBarのタイトル部分に画像を入れることができます。 override func viewDidLoad() { super.viewDidLoad() self.navigationItem.titleView = UIImageView(image: UIImage(named: "画像ファイル名")) } ・フリー素材DLサイト 著作権の関係から、自作の物やフリー素材を使いましょう。 ・アニメーションライブラリ「Lottie」 アクションにアニメーションを追加することで楽しいアプリになるかもしれません。 終わりに いかがでしたか? みなさんが楽しんでデザインに取り組んで頂けると嬉しいです!
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Swift API通信の綺麗なまとめ方

はじめに 使う時に手間がかかったりあまり整理されてないコードを業務でよく見かけます。 「文句を垂れるくらいなら良いコードを自分で示さないと!」と言う気持ちからまとめてみました。 今回ライブラリは使いません 良くも悪くもSwiftらしいコードで書いてみました。 人的ミスをコンパイルエラーで検知できる 設計書を読み込まなくても人やAPI毎の書き方の差異が出ない 以上を心がけています。細かなコメントは最後に書きます。 アプリ毎に 1つ 作成 ほとんどの場合はそのままコピペでOKです。 HTTPClient.swift import Foundation enum HTTPMethod: String { case GET case POST case PUT case PATCH case DELETE } protocol JSONSerializable: Codable { init(jsonData: Data) var jsonData: Data { get } static var dateFormatter: DateFormatter? { get } } extension JSONSerializable { init(jsonData: Data) { let decoder = JSONDecoder() if let formatter = Self.dateFormatter { decoder.dateDecodingStrategy = .formatted(formatter) } self = try! decoder.decode(Self.self, from: jsonData) } var jsonData: Data { return try! JSONEncoder().encode(self) } static var dateFormatter: DateFormatter? { return nil } } protocol RequestBody: JSONSerializable { // 設計として各リクエストに必ず定義させたい項目があればここに書く // <例> let timeoutSec: Int { get } } protocol ResponseBody: JSONSerializable { // 設計として各レスポンスに必ず定義させたい項目があればここに書く // <例> let message: String { get } } protocol API { static var urlPath: String { get } static var httpMethod: HTTPMethod { get } associatedtype Request: RequestBody associatedtype Response: ResponseBody } extension API { static var API: Self.Type { return Self.self } } protocol HTTPClient { typealias CustomHeader = (value: String, field: String) var baseURL: String { get } var headers: [CustomHeader] { get } } extension HTTPClient { /// ヘッダーに付加したい情報 var headers: [CustomHeader] { return [(value: "application/json", field: "Content-Type")] } /// 送信 /// - Parameters: /// - apiRequest: リクエスト /// - onSuccess: レスポンス取得成功時の処理 /// - onError: 個別エラー処理 func send<T: API>(_ requestBody: T.Request, to api: T.Type, onReceive: @escaping (_ response: T.Response) -> Void, onCatch: @escaping (Error) -> Void) { var urlRequest = URLRequest(url: URL(string: baseURL + T.urlPath)!) urlRequest.httpMethod = T.httpMethod.rawValue // GET 以外の場合のみ body を設定 if T.httpMethod != .GET { urlRequest.httpBody = requestBody.jsonData } // ヘッダー属性を付加 for header in headers { urlRequest.addValue(header.value, forHTTPHeaderField: header.field) } let task = URLSession.shared.dataTask(with: urlRequest, completionHandler: { data, response, error in if let error = error { onCatch(error) } else { let responseBody = T.Response(jsonData: data!) onReceive(responseBody) } }) // 実行 task.resume() } } 通信先のベースURL 毎に 1つ 作成 MyService : 対応するWebサービスを識別できる名前 baseURL: URLのうちAPIのパス分岐直前までの部分 _MyService_Client.swift /// MyServiceの部分には、対応するWebサービスを識別できる名前を入れる struct <#MyService#>Client: HTTPClient { let baseURL = <#"http://0.0.0.0:8000/api/v1"#> } API毎に 1つ 作成 MyAPI : 対応するAPI名 urlPath : APIまでのパス httpMethod : HTTPメソッド my_param: Type : bodyに含めるパラメーター フィールド名の命名規則は、API側に合わせるルールにしても問題ないはずです <例> myParam ではなく my_param にする どうしてもずらしたい場合は enum Key を使いましょう _MyAPI_.swift enum <#MyAPI#>: API { static var urlPath = <#"/path/to"#> // パスURL static var httpMethod = <#HTTPMethod.POST#> // HTTPメソッド struct Request: RequestBody { // リクエストパラメーター let <#my_param1: Type#> let <#my_param2: Type#> // ... } struct Response: ResponseBody { // レスポンスパラメーター let <#my_param1: Type#> let <#my_param2: Type#> // ... } /* Date型を含む場合は以下のようにフォーマット指定 */ // static var dateFormatter: DateFormatter? { // let formatter = DateFormatter() // formatter.dateFormat = "yyyy-MM-dd HH:mm:ss" // return formatter // } } 使用例 ある果物のデータを保存して、登録された ID と 日時 が返されるようなAPIがあるとします サービス名: GreenStore API名: save-fruit URL : "http://0.0.0.0:8000/api/v1/food/fruits" HTTPメソッド : POST 果物名 : りんご 価格 : 120 (円) 登録されるID : 99 登録日時: 2020/12/31 23:59:59 コード GreenStoreClient.swift /* グリーンストア */ struct GreenStoreClient: HTTPClient { // サービス名 let baseURL = "http://0.0.0.0:8000/api/v1" // ベースURL } SaveFruit.swift /* 果物データ保存 */ enum SaveFruit: API { static var urlPath = "/food/fruits" // パスURL static var httpMethod = HTTPMethod.POST // POST struct Request: RequestBody { let name: String // 果物名 let price: Int // 価格 } struct Response: ResponseBody { let fruit_id: String // ID let created_at: Date // 登録日時 } static var dateFormatter: DateFormatter? { let formatter = DateFormatter() formatter.dateFormat = "yyyy-MM-dd HH:mm:ss" // 日時のJSONフォーマット return formatter } } Sample.swift /* 通信実行 */ ... let request = SaveFruit.Request(name: "りんご", // 果物名: りんご price: 120) // 価格: 120 // リクエストを送信 GreenStoreClient().send(request, to: SaveFruit.API, onReceive: { response in // レスポンス取得成功 print(response.fruit_id, // ID: 99 response.created_at) // 登録日時: 2020-12/31T23:59:59 }, onCatch: { error in print(error.localizedDescription) // エラー出力 }) ... 細かいこだわりポイント - コピペのままでは使えないコードにはプレースホルダーを入れておく Xcodeでコードの中に <#プレースホルダー#> と書くと、お馴染みのプレースホルダーが作れます。 意外と知らない上級者の方も多いかも?! - 名前空間の表現はプレフィックスではなくenumを使う 個人的によく業務で見かける命名としてXXXRequest , XXXResponse の ペアを作らせる ルールがありますがあまり好ましくないと思っています。 プレフィックスによってこの 2つの関係性を認知 しているのは人間だけです。 片方だけ YYYRequest に変更して、もう一方は XXXResponse のまま残っていても気付けないことがあります。単純に数カ所書き換える手間もあります。 - コール方法を send() にした理由 よくライブラリなんかで HTTP.get("https://...")のような書き方がありますが, URL-HTTPMethod-API-パラメータの紐付けはメソッド実行よりも前に定義の時点で行たい理由から採用しませんでした。 ただ、別のメソッドを用意してラップすれば紐付けはいくらでも可能なので、こちらは否定する意図は全くありません。多くのサーバーサイドフレームワークのルーティングとも書き味が似ていて使いやすいと思います。 さいごに すべて机上で書いたので、抜けや漏れになっている観点があれば教えてください。 逆にこの書き方ではよく分からなくて教えて欲しいことがあればどうぞご質問ください。 私もまだまだ未熟者ですので、否定的なアドバイスでも大丈夫です。よろしくお願いします。 Swift5.5 で async/await が来たら書き換えます!
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

iPhone/iOSの画面下部のバーを消す

override var prefersHomeIndicatorAutoHidden: Bool { return true } ? フリーランスエンジニアです。 お仕事のご相談こちらまで rockyshikoku@gmail.com Core MLを使ったアプリを作っています。 機械学習関連の情報を発信しています。 Twitter Medium
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Swift】IBOutlet接続したプロパティのdidset内に直接遷移を実装すると画面が表示されない理由

結論 プロパティオブザーバーを利用して画面遷移を行う際は、イベントコントロールで正しく行えるようにする必要がある。直接遷移をさせずに、addTargetメソッドなどを用いる。 理由 IBOutletが接続されているViewに遷移されると、イニシャライズされて変数にIB部品のインスタンスが入ります。その際にプロパティオブザーバーとしてのdidsetが実行されるが、このdidset内にNavigationControllerによる別の画面への遷移を直接実装してしまうと、画面が遷移されてきたにも関わらず、画面を遷移するという状態になるため、意図している形には正しく遷移されない、もしくはエラーが生じる。 このエラーはaddTargetメソッドでイベントコントロールを図ることで解決できる。実行のタイミングを.touchUpInside、つまり枠内をタッチするイベントのような形にすることで、didset内に記述されていてもインスタンス生成時に実行されなくなる。 そして、実際の遷移はaddTargetメソッドでセレクタをし、objc属性のfunctionとしてNavigationControllerによる遷移を実装するということになる。 例 didset内に遷移を直接実装した場合 final class ViewController: UIViewController { @IBOutlet private weak var reStartButton: UIButton! { didSet { RootViewContorller.root.reStart() } } } //RootViewController.swift final class RootViewContorller { static let root: RootViewContorller = .init() private init(){} func reStart(){ UserDefaults.standard.isLogined = [true, false].randomElement()! showInitialView(window: window) } } この場合は次のようなDebugが表示される 2021-05-08 10:33:25.508263+0900 RootViewControllerTest[13610:776213] Unbalanced calls to begin/end appearance transitions for < RootViewControllerTest.SecondViewContorller: 0x7fd8bb524200 >. メッセージにあるように、遷移画面を起動/終了時の呼び出しが不安定ということ。遷移しても、即座に別の画面に遷移するような状態にあり、結果的に画面が表示されないためこのようなメッセージが表示されている。 didset内でaddTargetを利用した場合 final class ViewController: UIViewController { @IBOutlet private weak var reStartButton: UIButton! { didSet { reStartButton.addTarget(self, action: #selector(tapReStartButton(_:)), for: .touchUpInside) } } @objc private func tapReStartButton(_ sender: UIResponder) { RootViewContorller.root.reStart() } } //RootViewController.swift final class RootViewContorller { static let root: RootViewContorller = .init() private init(){} func reStart(){ UserDefaults.standard.isLogined = [true, false].randomElement()! showInitialView(window: window) } } この記述だと、プロパティオブザーバーとしてdidset内はインスタンス生成に実行されるが、addTargetメソッドは.touchUpInsideというIBパーツの枠内をタッチした場合に実行されるような形になっているため、遷移は実行さレナい。addTargetでは遷移の中身はactionパラメータのセレクターとして指定され、objc属性以下に書かれる。 addTargetメソッドは別のQiitaにまとめています。 まとめ プロパティオブザーバーを利用して画面遷移を行う際は、イベントコントロールで正しく行えるようにする必要がある。直接遷移をさせずに、addTargetメソッドなどを用いる。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む