20200728のSwiftに関する記事は12件です。

UIPageViewControllerをEnumで管理する

実務でコードを書いてる時に、UIPageViewControllerをEnumでいい感じに書いているコードに出会ったので、
自分のための備忘録も兼ねて、ご紹介します

実行環境

Xcode: Version 11.6

実装

PageViewController親の実装

PageViewControllerの親になるクラスには、Enumを使って、載せたいコンテンツを定義します
今回は例として以下のようなEnumを定義します
(Enumのコレクションの扱いを楽にする為に、CaseIterableを継承させています)
また、UIPageViewControllerはすでに宣言されていることとします(今回のサンプルはコードで宣言しています)

BasePageViewController.swift
enum Content: CaseIterable {
    case first, second, third

    var title: String {
        switch self {
        case .first:
             return "1番目のViewController"
        case .second:
             return "2番目のViewController"
        case .third:
             return "3番目のViewController"
        }
     }

     // ここで載せたいViewControllerを定義する
     func instantiated() -> UIViewController() {
        switch self {
        case .first:
             return ContentViewController(type: self)
        case .second:
             return ContentViewController(type: self)
        case .third:
             return ContentViewController(type: self)
        }
     }
}

その後、このような感じでViewControllerのインスタンスを作成して、配列を作成します

BasePageViewController.swift
private lazy var viewControllers: [UIViewController] = {
    return Content.allCases.map{ $0.instantiated() }
}()

あとは、それを定義してあるUIPageViewControllerに渡してあげます

BasePageViewController.swift
// ~~~
override func viewDidLoad() {
    super.viewDidLoad()

    // ~~~ 省略
    // ~~~

    // viewDidLoadが走るタイミングで表示させるViewControllerを指定する
    pageViewController.setViewControllers([viewControllers[0]], direction: .forward, animated: true)
}
// ~~~

残りはUIPageViewControllerのDataSourceの実装が必要となりますが、
これだけでEnumで管理できるようになりました?

PageViewController子の実装

Enumで定義した子のViewControllerでは以下のようにContentを受け取っています

ContentViewController.swift
private let contentType: BasePageViewController.Content

init(type: BasePageViewController.Content) {
    self.contentType = type
    super.init(nibName: nil, bundle: nil)
}

// 以下受け取ったContentを使って実装

完成

実際に上のコードを使って作ってみたものがこちら

最後

Enumで管理する方法はすごく便利ですね!
CollectionViewとかもEnumで管理すれば、またいい感じに書けますよね?

最後まで読んでいただいてありがとうございました?
何か修正等がございましたら、なんなりとお申し付けください??‍♂️

ソースコード

サンプルプロジェクトのソースコードはこちら
https://github.com/Take111/PageViewControllerSample

参考にさせてもらった記事

・CaseIterableとは何か(https://dev.classmethod.jp/articles/wwdc18-swift-4-2-case-iterable/)

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

Swiftで楽天レシピAPIを表示させてみた。〜Tableviewを作る編〜

やること

前回の続き
Tableviewを作る

Tableviewを作る

①Tableviewを作るコードを記入

    let tableView: UITableView = {
        let tv = UITableView()
        return tv
    }()

override func viewDidLoadの中にtableviewを表示させるコードを記入

view.addSubview(tableView)

③tableviewのサイズを指定

tableView.frame.size = view.frame.size

シミュレーター

スクリーンショット 2020-07-28 20.52.14.png

tableviewのデリゲートのメソットの設定

override func viewDidLoadの中にデリゲートのメソットを使えるようにするコードを記入

tableView.delegate = self
tableView.dataSource = self

これでデリゲートのメソットが使えるようになりました。

②classの一番上のところにprivate let cellId = "cellId"と記入

③一番下にデリゲート使うにあたって必ず必要なメソッドを記入

extension ViewController: UITableViewDelegate,UITableViewDataSource{
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return 10   //とりあえず10にしておきました。
            }
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    let  cell = tableView.dequeueReusableCell(withIdentifier: cellId, for: indexPath)
    cell.backgroundColor = .blue   //確認のため

    return cell
}
}

override func viewDidLoadの中にTableViewに上記のメソッドを反映させるコードを記入

tableView.register(UITableViewCell.self, forCellReuseIdentifier: cellId)

タイトルをつける

①Main.storyboardに移動してViewControllerを選択し、EditorEmbedldNavigationControllerを選択してNavigationControllerを設置する。
スクリーンショット 2020-07-28 21.42.32.png

②ViewController.swiftに戻ってoverride func viewDidLoadの中にタイトルを表示させるコードを入力する

navigationItem.title = "おすすめレシピ"

シミュレーター

スクリーンショット 2020-07-28 21.54.43.png

無事反映させることができました!
次はこの前のAPIを実際表示させてみたいと思います。

初心者なので上記のコードがベストなのかはわからないのですが勉強の記録として書かせていただきます。
より良い方法があればご教授いただけると嬉しいです。

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

【Swift】テーブルビューのスクロールを滑らかにする方法【KingFisher】

Qiita初投稿です!
iOSアプリ開発をしていると、UITableViewをかなり高頻度で使います。
今日はこのUITableViewを、より滑らかにスクロールできるように改善する方法を紹介します。

画像のキャッシュで滑らかに

スクロールがガクつく原因は色々考えられますが、セルに表示する画像のダウンロードに時間がかかって処理が重くなっているケースが多いです。

今回、同じ様に画像表示でアプリが重くなってしまった際にKingFisherというライブラリを使うことで、超簡単に画像をキャッシュしてサクサク動くようになりました。
UITableViewなどのアプリ内で、複数の画像を使う方にはオススメのライブラリです。

インストール

Cocoapodsを使いました。
pod 'Kingfisher'とPodfileに入れてinstallします。

使い方

ViewController.swift
let url = URL(string: "https://example.com/image.png")
imageView.kf.setImage(with: url)

上記のコードで画像のキャッシュを行うことができ、アプリがサクサク動くようになりました。

画像を初めて表示する際はURLからダウンロード、それ以降はキャッシュから表示するのでダウンロードを待たずに表示することが可能になります。

まとめ

画像のキャッシュと聞くとかなりハードな実装のイメージがありましたが、実際に行ってみると便利なライブラリもあり、短時間で行うことができました。

テーブルビューが滑らかに動かないと悩んでいる方は是非参考にしてみて下さい。

参考

https://github.com/onevcat/Kingfisher

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

[swift5]tableViewの基礎文法

投稿の経緯

現在独学でiOS開発を学習中。
学習教材はUdemyで人気の高かった【iOS13対応】未経験者がiPhoneアプリ開発者になるための全て iOS Boot Campを使用。(サイトは下記URL)
https://www.udemy.com/course/ios13_swift5_iphone_ios_boot_camp/

学習内容をアウトプットします!

tableViewとは

要約するとリスト型のパーツ。tableViewControllerはリスト型のコントローラーということ。

tableViewのデリゲート宣言

UITableViewDelegate, UITableViewDataSource の2つが必要。

記述するとエラーが発生するが、それはtableViewの実装に必要なメソッドが
不足しているというエラーで、Xcodeの補完に沿ってメソッドを作成すれば解消される。

tableViewの構築に必要なメソッド

①セルのセクションを決めるメソッド

ViewController.swift
func numberOfSections(in tableView: UITableView) -> Int {

}

②セルの数を決めるメソッド

ViewController.swift
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {

}

③セルを構築する際に呼ばれるメソッド

ViewController.swift
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {

}

①〜③は上から順に読み込まれる。

④セルの高さを決めるメソッド

ViewController.swift
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {

}

⑤セルがタップされた時に呼ばれるメソッド

ViewController.swift
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {

}

備考

tableViewの更新方法▼

ViewController.swift
tableView.reloadData()

セルのハイライトを消す方法▼

ViewController.swift
cell.selectionStyle = .none
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Appleに開発者登録をしたときに、まずすること・してはいけないこと

 Appleは最近セキュリティーに厳しく、開発者登録を行い、公証を受けたアプリケーションでないとこのアプリケーションは悪意を持つソフトの可能性がある旨のメッセージが出るようになりました。

 そこで開発者登録し、公証を受けた野良アプリを作る際に最初にやるべき事、やってはいけない事を自分の失敗から、同じ失敗をしないように、この記事を読んで下さった方のために纏めたいと思います。

開発者アカウントをXcodeに追加する

 証明書の作成はdeveloper.apple.comのサインイン後のページからもできますが、
Xcodeのアカウント環境設定から行うのが確実で良いかと思います。

 まずは、環境設定のAccountsタブへ移動し、画面一番したの+ボタンで開発者登録したメールアドレスを追加します。
Account.png

開発者証明を作成する

 AppStoreのアカウント作成は色々資料があると思うので割愛します。
今回の目的は、公証を受けた野良アプリ(自サイトで配布するアプリケーション)なので、作成する証明書は、Developper ID Applicationになります。
Certificate.png
 これをXcodeから作成するには、追加したアカウントの右側のペインの右下にあるManage Certificateボタンを押します。
すると表示されるのが下の様なウィンドウになると思います。
僕の場合は、すでにアカウントを作成しているので黒文字・グレイ文字の行がいくつかありますが最初は行が一つも存在しないかと思います。

ここで、画面左下の+ ∨ボタンを押して、Developper ID Applicationを選んで下さい。

作成した開発者証明書を保存する

 上の作業で、Developer ID Applicationに黒文字で証明書が一つ追加されたと思います。
ここで、まず最初にこの証明書をエクスポートして保存しておきます。
 証明書の保存方法は黒地のDeveloper ID Applicationを右クリックして、Export Certificate...を選びます。
保存場所は自分がわかる場所ならどこでも構いません。パスワードも人に渡すものでもないのでそう難しいパスワードで無くても良いと思います。
SaveCertificate.png
パスワードと、チェック用にもう一度同じパスワードを入力したら、Saveボタンを押すと付けた名前.p12というファイルが作成されます。これがプライベートキー付き証明書になります

エクスポートした証明書から証明を復旧する

 作成された.p12ファイルをダブルクリックすると、KeyChainに証明書が追加されます。
確認は、KeyChain Accessアプリケーションのログイン証明書で右側のペインに読み込んだ証明書が表示されます。
KeyChain.png

余談かつ戒め

 注意すべきはこの開発者証明書、取り消しが出来なくて、しかも作成できる上限が5つと少し少なめです。
そのため、マシンをクリーンインストールした・買い換えたなどで証明書を発行しまくると、あっという間にパンクしてしまいます。
それを防ぐためにも証明書はこの、プライベートキーがある状態で保存しておき、特別な理由がない限りそれを再インポートして使うべきです。

 なぜ、Appleの開発者ページで証明書を作成し、ダウンロードしたものをダブルクリックでキーチェーンに追加しないか?というと、ダウンロードされた証明書にはプライベートキーが含まれていないため、ダウンロードした証明書をバックアップして安心してしまうのが危険だからです。

そこで、Xcodeでプライベートキー付きの証明書をダウンロードして、余計なファイルを作らず、それをエクスポートした「プライベートキー付き証明書」(=.p12ファイルで.cerファイルではないことに注意)を管理することを強くお勧めします。

 証明書の復旧が必用なときはこのファイルをダブルクリックしてKeyChainに追加して下さい。
そして、このファイルはマシンが不慮の事態でクラッシュしたときに影響を受けないように、物理的に別なディスクやUSBメモリーカードなどにコピーを作っておくのが良いと思います。

お付き合い下さりありがとうございました

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

iOS: gRPC(Protocol Buffers) + ReactiveSwiftのサンプル

サンプルの説明

  • .protoファイルをcocoapodsを利用してビルドする方法です。(grpc-swiftではありません。)
  • 生成されたObjective-Cのコードを、Swiftから利用します。
  • gRPCでAPIにアクセスする処理をReactiveSwiftのSignalProducer, Signalを返すようにラップしています。

サンプルコードのリポジトリ

https://github.com/yusuke-imagawa/iOS_gRPC_ReactiveSwift_sample
.protoファイル, gRPC, ReactiveSwiftの連携部分だけを実装しています。
UIは実装していません。

使い方

  • pod installを実行。

cocoapodsで.protoファイルから、コードを生成するための設定

.protoファイル

user.proto
report.proto
push_notify.proto
commons.proto
chat.proto
calling.proto
block.proto
account.proto

Podfile

# Uncomment the next line to define a global platform for your project
platform :ios, '13.2'

target 'TalkingSns' do
  # Comment the next line if you don't want to use dynamic frameworks
  use_frameworks!

  # Pods for Talking
  pod 'RealmSwift', '4.4.0'

  # GRPC_Client
  pod 'RemoteClient', path: './RemoteClient'

  pod 'ReactiveSwift', '~> 6.1'
  pod 'ReactiveCocoa', '~> 10.1'

  pod 'CocoaLumberjack/Swift'

  target 'TalkingSnsTests' do
    inherit! :search_paths
    # Pods for testing
  end

  target 'TalkingSnsUITests' do
    # Pods for testing
  end

end

RemoteClient/RemoteClient.podspec

Pod::Spec.new do |s|
  s.name     = "RemoteClient"
  # .protoファイルの更新時に、versionを変更して pod install する。
  s.version  = "0.0.21"
  s.license  = "New BSD"
  s.authors  = { 'imagawa' => 'test@example.com' }
  s.homepage = "http://example.com"
  s.summary = "grpc client"
  s.source = { :git => 'https://github.com/yusuke-imagawa/talking-ios.git' }

  s.ios.deployment_target = "7.1"
  s.osx.deployment_target = "10.9"

  # Base directory where the .proto files are.
  src = "./proto"

  # Run protoc with the Objective-C and gRPC plugins to generate protocol messages and gRPC clients.
  s.dependency "!ProtoCompiler-gRPCPlugin", "~> 1.0"

  # Pods directory corresponding to this app's Podfile, relative to the location of this podspec.
  pods_root = '../Pods'

  # Path where Cocoapods downloads protoc and the gRPC plugin.
  protoc_dir = "#{pods_root}/!ProtoCompiler"
  protoc = "#{protoc_dir}/protoc"
  plugin = "#{pods_root}/!ProtoCompiler-gRPCPlugin/grpc_objective_c_plugin"

  # Directory where the generated files will be placed.
  # dir = "#{pods_root}/#{s.name}"

s.prepare_command = <<-CMD
  #{protoc} \
      --plugin=protoc-gen-grpc=#{plugin} \
      --objc_out="./src" \
      --grpc_out="./src" \
      -I #{src} \
      -I #{protoc_dir} \
      #{src}/*.proto

CMD

  # Files generated by protoc
  s.subspec "Messages" do |ms|
    ms.source_files = "src/*.pbobjc.{h,m}", "src/**/*.pbobjc.{h,m}"
    ms.header_mappings_dir = '.'
    ms.requires_arc = false
    # The generated files depend on the protobuf runtime.
    ms.dependency "Protobuf"
  end

  # Files generated by the gRPC plugin
  s.subspec "Services" do |ss|
    ss.source_files = "src/*.pbrpc.{h,m}", "src/**/*.pbrpc.{h,m}"
    ss.header_mappings_dir = '.'
    ss.requires_arc = true
    # The generated files depend on the gRPC runtime, and on the files generated by protoc.
    ss.dependency "gRPC-ProtoRPC"
    ss.dependency "#{s.name}/Messages"
  end

  s.pod_target_xcconfig = {
    # This is needed by all pods that depend on Protobuf:
    'GCC_PREPROCESSOR_DEFINITIONS' => '$(inherited) GPB_USE_PROTOBUF_FRAMEWORK_IMPORTS=1',
    # This is needed by all pods that depend on gRPC-RxLibrary:
    'CLANG_ALLOW_NON_MODULAR_INCLUDES_IN_FRAMEWORK_MODULES' => 'YES',
  }
end

各APIのクライアント

PushNotifyApiClient.swift
AccountApiClient.swift
BlockApiClient.swift
CallingApiClient.swift
ChatApiClient.swift
ReportApiClient.swift
UsersApiService.swift

例:ReportApiClient

import RemoteClient
import ReactiveSwift

class ReportApiClient {

    static let shared = ReportApiClient()

    private init() {}

    private var service: ReportService? {
        return GrpcServiceLoader.shared.getReportService()
    }

    func reportUser(toUserId: Int) -> SignalProducer<Bool, Error> {

        return SignalProducer<Bool, Error> {
            (observer, lifetime) in

            let request = PostReportRequest()
            request.toUserId = Int64(toUserId)

            self.service?.rpcToPostReport(with: request) {
                (response: GPBEmpty?, error: Error?) in
                if let error = error {
                    observer.send(error: error)
                    return
                }
                observer.send(value: true)
                observer.sendCompleted()
            }.startWithHeaders()
        }
    }
}

request時にユーザー認証用のheaderを設定

  • この処理は必須ではないです。
  • ユーザーの認証情報の設定箇所を共通化するため、独自に実装しています。

GRPCProtoCall+extension.swift

extension GRPCProtoCall {

    func startWithHeaders() {
        if let userId = CurrentUserService.getCurrentUserId(),
            let apiToken = CurrentUserService.getApiToken() {
            requestHeaders.addEntries(from: ["user_id":String(userId)])
            requestHeaders.addEntries(from: ["api_token":apiToken])
        }
        start()
    }
}

request時に startWithHeaders() を呼び出す。
BlockApiClient.swift

    func block(toUserId: Int) -> SignalProducer<Bool, Error> {

        return SignalProducer<Bool, Error> {
            (observer, lifetime) in

            let request = BlockRequest()
            request.toUserId = Int64(toUserId)

            self.service?.rpcToBlock(with: request) { (response: GPBEmpty?, error: Error?) in
                if let error = error {
                    observer.send(error: error)
                    return
                }
                observer.send(value: true)
                observer.sendCompleted()
            }.startWithHeaders()
        }
    }

参考記事

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

Xcode11でストーリーボードなしで画面ロードする方法

概要

比較して学ぶRxSwift入門という本1がKindle Unlimtedで読めるので読み進めているところです。
とてもわかりやすく良い書籍なのですが、最新のXcode11ではつまづく点があるのでその解決方法を書きます。

この書籍のサンプルコードを実行するためには「開発を加速させる設定」なる設定が必要で、それはストーリーボードをプロジェクトから削除した上でxibファイルで作成した画面をアプリ起動時にロードする設定を指すのですが、書籍に書いてある通りに設定してもXcode11ではうまく動きません。2

この原因はどうやらXcode11から導入されたSceneDelegateが関係しているようで、これに関連するファイル・コード・設定を削除することで解決できます。

この、SceneDelegateに関連するファイル・コード・設定を削除する手順を含めた「開発を加速する設定」を以下に示します。

Xcode Version 11.5 (11E608c) で確認しています。

手順

おおまかな手順は以下の通りです。
1. Single View App のプロジェクトを作成する
2. Main.storyboadSceneDelegate.swift を削除する
3. Info.plist を編集する
4. AppDelegate.swift を編集する
5. ViewController.xib を作成する

手順ごとに具体的に見ていきます。

1.Single View Application のプロジェクトを作成する

  1. XCodeを起動し、File>New>Projectをクリックすると出てくるダイアログで Single View App を選択し、Next をクリックします。
  2. 次のダイアログでプロジェクト名などを設定します。この時、 LanguageSwiftUser InterfaceStoryboardを選択しておきます。設定したら Nextをクリックします。
  3. ファイルの保存先を指定して Create をクリックします。3

2.Main.storyboad、SceneDelegate.swiftを削除する

普通の手順です。画面左端のProject NavigaterからMain.storyboad と、SceneDelegate.swift を削除します。(Move To Trashを選択します) 4
スクリーンショット 2020-07-28 14.14.49.png

3. Info.plistを編集する

画面左端のProject NavigaterからInfo.plistを選択し、設定リストを表示します。
次に以下のエントリを選択し、マイナス記号をクリックして削除します。
- Application Scene Manifest 5
- Main storyboard file base name
スクリーンショット 2020-07-28 14.19.11.png

4. AppDelegate.swiftを編集する

画面左端のProject NavigaterからAppDelegate.swiftを開き、コードを全て削除した後、以下の内容をコピー&ペーストします。

import UIKit

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {

    var window: UIWindow?


    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
        self.window = UIWindow(frame: UIScreen.main.bounds)
        let navigationController = UINavigationController(rootViewController: ViewController())
        self.window?.rootViewController = navigationController
        self.window?.makeKeyAndVisible()
        return true
    }
}

ちなみに、Xcode11では初期状態ではコードは以下のようになっていますが、ストーリーボードを使用しない場合、赤枠の部分のSceneDelegate関連のメソッドは不要となるため削除します。
スクリーンショット 2020-07-28 14.35.06.png

5. ViwController.xibを作成する

この手順は書籍と同じです。
画面左端のProject Navigaterを右クリックし、NewFile>ViewでViewを選択し、Nextをクリック、次の画面で ファイル名(Save As:)を ViewController.xibとして Createをクリックします。
次にビューエディタの左側のPlaceholders>File's Ownerを選択し、ClassをViewControllerに変更します。
スクリーンショット 2020-07-28 14.49.47.png
次にOutletsのviewとViewControllerのViewを接続します。
スクリーンショット 2020-07-28 14.54.44.png

確認

Build&Runしてシミュレータに以下の画面が出ればOKです。
スクリーンショット 2020-07-28 15.08.56.png

念のためViewController.swiftViewDidLoad()にブレークポイントを設定して狙い通りの画面がロードされていることを確認するのも良い考えだと思います。
スクリーンショット 2020-07-28 15.04.01.png

画面がうまく出てこない場合

上記手順で ViewController.swiftの中で定義されたクラス名やViewController.xibのファイル名を任意のものに変更した場合、以下のような画面が出てうまくいかないかもしれません。
スクリーンショット 2020-07-28 15.15.01.png
*.xibのファイル名とそれに紐づくViewControlerのクラス名が一致しない場合に上記の現象が起こります。これらは一致させる必要があるようです。

以上


  1. https://www.amazon.co.jp/dp/4844398792/ この文中で「書籍」と呼ぶ場合はこれを指します。 

  2. シミュレータでのアプリ起動時に実行時エラーが発生する Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'Could not find a storyboard named 'Main' in bundle NSBundle <略> (loaded)' 

  3. 普通の手順ですが、書籍と違い、Xcode11ではUser Interface で SwiftUIが選択できてしまうので注意します。 

  4. SceneDelegate.swift も削除するところが書籍と異なります。 

  5. Xcode11から追加された項目のようです。書籍には登場しません。 

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

Create ML Object DetectionにピッタリのAnnotation tool(アノテーションツール) IBM Cloud Annotations。

Create MLのデータ形式をサポート

物体検出(Object Detection)用の Bounding Box アノテーションツールは、
・labelImg
・VoTT(TBC)
などローカルで使うものがGitHubで人気です。

でも、Create MLでトレーニングするなら、IBM Cloud Annotationsがとっても便利です。

なぜか。

それは、Create MLの注釈データ形式(json)で出力できるからです。
labelImgやVoTTは、出力形式がxml、txt、csvなので、Create MLに食わせるにはjsonに変換する必要がありますが、IBM Cloud Annotationsを使うと出力したフォルダごとそのままCreate MLのデータとして指定できます。
(例↓)

PetsAnnotation.json
[{"image":"pets0.jpg","annotations":[{"label":"cat","coordinates":{"x":348,"y":380,"width":113,"height":74}},{"label":"dog","coordinates":{"x":589,"y":390,"width":122,"height":75}}]},{"image":"pets1.jpg","annotations":[{"label":"cat","coordinates":{"x":233,"y":670,"width":238,"height":169}},

使い方もシンプルで直感的(しかも無料)

1、画像をアップロード(ドラッグ&ドロップ)
2、マウスドラッグで境界ボックスをつける
3、「File」から「Export As Create ML」を選ぶ。

これで、画像とjsonファイルがセットになったフォルダがローカルに保存され、そのままCreate ML Object Detection のデータとして参照できます。

欠点

回線によっては画像アップロードにかかる時間にストレスがあるかもしれません。

Happy Machine Learning✨!!

MLBoys Make Your World Beautiful with ML

MLBoysのTwitterをフォローしてください。お願いします。
だいすけ https://twitter.com/JackdeS11
ゆうじ https://twitter.com/oka_yuuji

Core MLやCreate ML、Visionを使ったアプリを作っています。
エッジデバイス(iOS)で機械学習のモデルを使いたい、などのありましたら、こちらのメールまでご相談ください。
rockyshikoku@gmail.com

Looks Good To Me(わるくないね)おねがいします。
            ↓

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

iOS Simulator でタイムゾーンを変更して実行する方法

タイムゾーンを越えたテストなど、iOS Simulator のタイムゾーンを変更したいときってありますよね?

さっとググると、MacOSのタイムゾーンを変更しろ!なんて情報がありますけど、MacOS の他のアプリケーションにも影響しそうで積極的にはやりたくありません。

別の方法はあるのでしょうか?

いろいろと調べたところ、結論からいうと、ビルド時の環境変数 TZ を変更することで、ビルドしたアプリ限定でタイムゾーンを変更できるようです。
正確には Simulator のタイムゾーンは変更されないのですが、僕が確認したかった範囲ではこれで十分だったので有用と思いましてまとめました。

手順

まずは、xcode 左上のアプリ名のところを選択して、「Edit Scheme...」を選びます。

image.png

次に、、下図のように選択します。

image.png

③ のところには、Name に TZ、Value にタイムゾーンを示す文字列を入力します。

Value の指定は、tz database にある Asia/Tokyo といった文字列や、JST, PST のような略語文字列でも扱ってくれるようです。タイムゾーン指定の標準については詳しくないのですが、tz database から選ぶのが無難と思います。

確認方法と注意点

まずは注意点。この方法だと、正確には 、実行しているアプリ内でのみタイムゾーンが変化し、iOS Simulator 内のタイムゾーンは変化しません。
なので、iOS 上部のステータスバーの時刻は変わりません。ですが、アプリ内の時刻は指定したタイムゾーンで処理されています。

確認した方法は次のとおりです。

まずは、なにも指定無し(= MacOS 側のシステム設定)です。システム設定は、Asia/Tokyo です。

適当なところに以下のコードをコピペします。

    let today = Date.today()
    let df = DateFormatter()
    df.dateFormat = "hh:mm Z"
    df.timeZone = TimeZone.current
    print(today)
    print(df.timeZone!)
    print(df.string(from: today))

実行すると、以下のようにコンソールに表示されます。日本時間で正しく表示されています。

2020-07-28 03:19:29 +0000
Asia/Tokyo (current)
12:19 +0900

次に、TZAmerica/Los_Angeles を設定して実行してみます。

2020-07-28 03:19:55 +0000
America/Los_Angeles (current)
08:19 -0700

適切にタイムゾーンが変更されていることを確認できました。

同様に、ちょっとマイナーな IRST(イラン標準時)を4文字略称で設定してみます。

2020-07-28 03:20:36 +0000
Asia/Tehran (current)
07:50 +0430

こちらも確認することができました。

参考文献

https://stackoverflow.com/questions/1699671/how-to-change-time-and-timezone-in-iphone-simulator

※ この記事は、xcode 11.4.1 で確認しています。

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

Swift オプショナル型とアンラップを理解

Swiftでは変数の宣言時に、何も値が無い状態を許すか許さないかを、データ型で指定する必要がある。「オプショナル型」はデータ型の種類のひとつです。

オプショナル型とは

値が無い状態を許すデータ型はオプショナル型で、変数宣言時のクラス名の後に「?」か「!」を指定します。「?」か「!」を付けない宣言は、値がない状態を許さない「非オプショナル型」になります。一般的なオプショナル型は「?」を利用します。 「?」も「!」は両方とも「nil」を保持することが許されてますが、違いはわかりません。。すみません。複雑らしいので。。 最初はオプショナル型を宣言する場合は「?」を利用すればいいと覚えておいて大丈夫かと。

アンラップとは

オプショナル型の変数やメソッドを、安全に取り扱う手法をアンラップといいます。 宣言時以外で「?」や「!」を見かけたら、アンラップを行っているんだなと理解できれば問題ないかと。。

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

【小ネタ】Realm Studioを使ってGUIでRealmをいじる

はじめに

SwiftでWebsocketのチャットを触っているときに、RealmSwiftでいちいちデータの状態をいろいろ変えたりするのが面倒だったので、ラクなやり方を探してみました。
データの登録や削除や編集がGUIで簡単にできました!

Realm Studio

Realm Studioをダウンロードします。

image.png

僕はMacだったのでMac用のものをダウンロードしました。
開発元が確認できませんみたいなのが表示されたら、システム環境設定セキュリティとプライバシーのところを確認してみるといいと思います。

Realmファイルを開く

次は、Realm Studioで編集するためのRealmファイルを見つけます。
こちらの記事がとても参考になりました。

print(Realm.Configuration.defaultConfiguration.fileURL!)

このprint()で出力されたパスをfinderでcommand + shift + Gで開き、そのディレクトリをRealm Studioで開きます。
これでDBの管理画面のようなものが表示されると思います。

image.png

さいごに

本当はチャットの部分も載せたかったのですが、まだ公開ができないのでRealm Studioの導入の部分だけ載せておきました。
ありがとうございました。

参考リンク

https://qiita.com/i_nak/items/5d6062333b205275b85b

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

[Swift] var-forパターンを避けよう

はじめに

var-forパターンは既存の言葉ではありません。
このアンチパターンよく見かけるので自分でvar-forパターンと勝手に命名しました。
コードレビューとかで「これvar-forパターンだよね」という感じで使えるかもしれません。

本記事の内容はSwiftの初心者向けです。

var-forパターンとは

以下のような一時変数varとfor文(forEach、while、repeat-whileなども含む)を利用したロジックです。

例題)1から10までの整数を3倍して、6の倍数のみの配列を生成

var result = [Int]()
for number in 1...10 {
    let tripleNumber =  number * 3
    if tripleNumber % 6 == 0 {
        result += [tripleNumber]
    }
}

よくあるのが、配列から特定の条件を満たした別の配列を作る時です。

var-forパターンの置き換え

たいていのvar-forパターンは以下のようにmapやcompactMap、filter、reduceなどの高階関数で置き換えられます。
※高階関数がわからない方向けの記事:イメージで理解するSwiftの高階関数(filter, map, reduce, compactMap, flatMap)

let result = (1...10)
    .map { $0 * 3 }
    .filter { $0 % 6 == 0 }

var-forパターンのデメリット

可読性が低い

読み手の頭の中を想像して、var-forパターンのロジックを日本語にしてみます。

var result = [Int]() // 要素がInt型のresultという名前の配列を定義し、空で初期化する
for number in 1...10 { // 1から10までループさせ
    let tripleNumber =  number * 3 // numberを3倍したtripleNumberという名前の定数を用意する
    if tripleNumber % 6 == 0 { // もしもtripleNumberが6で割り切れたら、
        result += [tripleNumber] // resultにtripleNumberを追加する
    } // tripleNumberが6で割り切れなければ何もしない
}

次に、高階関数を利用したパターンです。

let result = (1...10) // 1から10までの整数を
    .map { $0 * 3 } // 3倍して、
    .filter { $0 % 6 == 0 } // 6の倍数のみにした定数resultを定義

上記を比較してみます。

var-forパターン

要素がInt型のresultという名前の配列を定義し、空で初期化する、1から10までループさせ、numberを3倍したtripleNumberという名前の定数を用意する、もしもtripleNumberが6で割り切れたら、resultにtripleNumberを追加する、tripleNumberが6で割り切れなければ何もしない

高階関数パターン

1から10までの整数を3倍して、6の倍数のみにした定数resultを定義

どちらがわかりやすいでしょうか?
後者の方が例題そのものの性質をよく表現しており、より人間が読みやすく、宣言的なコーディングスタイルになっています。
(プログラミングパラダイムでいうと、前者は命令型プログラミング、後者は宣言型プログラミングと分類できます)

修正がしにくい

例題にこんな仕様変更があったらどう修正するでしょうか?

1から10までの整数を3倍して、6の倍数のみにした先頭要素の2つの配列を生成

var-forパターンの場合、このような修正が思いつきますが、要素数が2個未満の場合に要素を追加するというのはあまり宣言的ではなく、result.count =< 2のような間違ったコードを書いてしまう可能性もあります。

var result = [Int]()
for number in 1...10 {
    let tripleNumber =  number * 3
    if tripleNumber % 6 == 0 && result.count < 2 {
        result += [tripleNumber]
    }
}

高階関数パターンであれば、以下のようにprefixを追加するだけです。

let result = (1...10)
    .map { $0 * 3 }
    .filter { $0 % 6 == 0 }
    .prefix(2)

バグを生みやすい

修正がしにくいにも書きましたが、可読性が悪い、変更がしづらいというのはバグを生みやすいコードです。
また、let定数でなく、var変数のように、コード内に値が変化する箇所があるというのもバグを生みやすい原因になります。

よりバグの少ないない安全なプログラムを作るには変化する箇所をできるだけ減らすことが有効です。
状態がなく、変化しないものはテストパターンが少なく済むので、バグを見逃す確率がぐっと減ります。

  • varよりもletをなるべく使う(変化させない)
  • class(参照型)よりもstruct/enum(値型)を使う(変化を伝播させない)
  • プリミティブ型よりも独自型やenumを利用する(変化する値のとりうる範囲を制限する)

まとめ

  • var-forパターンよりも高階関数を利用する
  • var-forパターンは可読性、修正しやすさ、安全さが劣る場合がある
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む