20200728のiOSに関する記事は9件です。

【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で続きを読む

ゲームアプリ制作におけるベジェ曲線の可能性

はじめに

アプリの絵がぼやける理由

アプリを遊んでいて「絵がぼやけてる」と感じたことはありませんか?
解像度の大きな端末に乗り変えたら、お気に入りのアプリの表示がボケボケで、切ない気分になった人は少なからずいるのではないでしょうか?

これは、アプリの画像素材のほとんどがペイント形式で作成されているからです。
ペイント形式とは画素を並びで保持する形式なのですが、拡大や回転による品質の劣化が起こります。

例えば、1280x720 のキャラクタの立ち絵がペイント形式で用意されていて、アプリが画面全体に表示させたとします。
解像度 1280x720 の端末であればクッキリと表示されますが、2560x1440 の端末だと画素が引き延ばされて画像がぼやけてしまうことになります。

アプリの文字がぼやけない理由

さて、こうも思ったことはありませんか?

「文字だけやけにきれいに見える」

これは、文字がドロー形式で表示されているからです。
ドロー形式とは、データとして座標や色情報などを保持しておき、計算で絵を生成する形式のことです。

先の例の場合、1280x720 の端末でも 2560x1440 の端末でも、解像度に合わせた素材が実行時に生成されるため、文字がぼやけずきれいに表示されるというわけです。

絵も文字と同じ形式にしたらどうか

ペイント形式のデータが端末によってぼやけてしまうのは、ゲーム制作者にとって悩ましい問題です。
一般的な対策として、低解像度の端末向けに小さな画像、高解像度の端末向けに大な画像を用意しておき、端末の性能に応じて切り替えて利用するというものがあります。

ただし、この対応はデータの管理や作成が煩雑になりますし、スマホの性能の向上とイタチごっこになる側面があります。

そこで素朴な疑問です。
アプリの絵素材も文字と同じく、ドロー形式で作成してみたらどうでしょうか?
ものは試しです。ドロー形式でゲーム素材を作成する場合、どんなことができるか可能性を探ってみましょう。

ベジェ曲線

ドロー形式の基本要素が「ベジェ曲線」です。
ベジェ曲線とは「座標+アルファ」で曲線を表現する手法です。

アンカーポイント

ベジェ曲線では座標のことを「アンカーポイント」と呼びます。
このアンカーポイントを複数置き、順番につなげることでパス(線)を表現します。

↓ 図1:アンカーポイントを4つ配置(4つの黒い四角)
ss00_00

↓ 図2:アンカーポイントを繋げてパスを構成(4本の線)
ss00_01

アンカーポイント0→1、1→2、2→3、3→0の順番でパスが引かれ、菱形が描画されました。

方向線

さて、アンカーポイントだけだとまっすぐなパスしか表現できないので、パスを曲げるための「+アルファ」が必要です。
パスを曲げるには、アンカーポイントに「方向線」と呼ばれる2種類のベクトルを付与します。

*誘導ベクトル*
1つ目の方向線は、そのアンカーポイントから「出発するパスを誘導する」効果を持ち、パスの引き始めほど強く影響します。
ここでは「誘導ベクトル」と呼びます。

↓ 図3:アンカーポイントに誘導ベクトルを追加(4本の青い矢印)
ss00_02

それぞれのアンカーポイントから伸びる誘導ベクトル(青い線)に沿って、パスの描き始めが曲げられました。

*誘引ベクトル*
2つ目の方向線は、そのアンカーポイントに「到着するパスを誘引する」効果を持ち、パスの終わり際ほど強く影響します。
ここでは「誘引ベクトル」と呼びます。

↓ 図4:アンカーポイントに誘引ベクトルを追加(4本の赤い矢印)
ss00_03

誘導ベクトルとは逆に、アンカーポイントの誘引ベクトル(赤い矢印)に引っ張られて、パスの終わり際が曲げられました。

誘導ベクトルと誘引ベクトルは、1つのアンカーポイントに両方指定することもできます。

↓ 図5:誘導ベクトルと誘引ベクトルで円の描画
ss00_04

アンカーポイントと方向線を工夫することで、自由自在にパスを描けるのがベジェ曲線の強みです。

↓ 図6:試しに描いた歯っぽいもの
ss00_05

では、ベジェ曲線を土台とした描画環境を実装し、アプリ上で表示できる絵素材を作成してみましょう。

素材作りの流れ

素材(画素)の生成はベジェ曲線で行うわけですが、それ以外のところは一般的な 2D 素材作成の流れを想定します。
いろいろなゲームで採用されている Live2D っぽく、パーツ別に素材を作成して組み合わせ、1枚の絵としてのバリエーションを出すことを目標としましょう。

下絵の作成

パーツわけした下絵を作成してエディタに取り込みます。
(※Live2Dサンプルを真似してミクさんを作成してみます)

↓ 図7:パスを作成する際の下絵(パーツ単位で素材を作るのでバラバラ)
ss01_00

パーツ素材の作成

エディタ上で下絵をトレースしていきます。

↓ 図8:目(アイラインと白目)の素材作成
ss01_01

↓ 図9:シャツの素材作成
ss01_02

パーツ素材の組み合わせ

バラバラに作成したパーツを組み合わせることで、絵を構築していきます。

↓ 図10:顔パーツの組み合わせ
ss01_03

↓ 図11:胴体パーツの組み合わせ
ss01_04

↓ 図12:ミクさん完成
ss01_05

サンプルアプリ

作成した素材の表示を確認するためのサンプルアプリが GitHub へアップロードしてあります。

サンプルアプリの環境一式はこちら

iOS / Android それぞれのプロジェクトがあるので、興味のある方はビルドして実行してみてください。

サンプルアプリで確認できる表現

ベジェ曲線的に実装した表現手法として、サンプルアプリで確認できる内容は下記となります。

移動値補正パラメータ

アンカーポイントは移動値(風やキャラクタの動き)からの影響されやすさを保持します。
髪や布などのアンカーポイントにこの補正値を高めに適用することで、キャラクタの動き(移動)に沿った変化をさせることが可能です。
(※サンプルプアプリの「SWY」と「MOV」ボタンの機能です)

↓ 図13:移動値による髪などのなびき(移動方向を目で追います)
ss_06

体型補正パラメータ

アンカーポイントは体型パラメータからの影響されやすさを保持します。
各アンカーポイントにこの補正値を適用することで、体を細くしたり、背をのばしたりすることが可能です。
(※サンプルプアプリの「H」と「V」ボタンの機能です)

↓ 図14:体型補正による変化:体が細い〜太い
ss_00

↓ 図15:体型補正による変化:身長が低い〜高い
ss_01

パーツ毎のサイズパラメータ

パーツはサイズパラメータからの影響されやさを保持します。
各部位のサイズをまばらに変え、パーツ毎に個性の表現が可能です。
(※サンプルプアプリの「S」ボタンの機能です)

↓ 図16:サイズパラメータによる変化(ツインテールを大きめに補正)
ss_03

パーツ毎の回転パラメータ

パーツは回転パラメータからの影響されやすさを保持します。
腕や肘などの稼働領域の影響されやすさとなり、絵によって受け入れられる角度を制限することで、関節があらぬ方向へ曲がらないように制限することが可能です。
(※サンプルプアプリの「T」ボタンの機能)

↓ 図17:回転パラメータによる変化
ss_02

擬似的な傾け

絵全体を擬似的に傾けることで、画像に左右/上下に傾いた印象を与えることが可能です。
(※サンプルプアプリの「LR」と「UD」ボタンの機能)

↓ 図18:傾き適用による変化:左右
ss_04

↓ 図19:傾き適用による変化:上下
ss_05

ストロークの切り替え

パスの描画ブラシを変えることで、ストロークの差し替えが可能です。
(※サンプルプアプリの「STR」ボタンの機能)

↓ 図20:ストロークの切り替え
ss_07

パレットの切り替え

厳密にはベジェ曲線の機能というわけではありませんが、処理の単純化と容量を浮かせるために画像をパレット形式で管理しているため、パレット差し替えが可能です。
(※サンプルアプリの「COL」ボタンの機能)

↓ 図21:パレットの切り替え
ss_08

素材の差し替え

こちらも厳密にはベジェ曲線の機能というわけではありませんが、ベジェ曲線(ドロー形式データ)の場合、ストロークや塗りの表現を統一できるので、素材の差し替えの自由度を高められます。
(※サンプルアプリの「COSTUME」ボタンの機能)

↓ 図22:基本素材によるアニメ
ss_13

↓ 図23:胴体をネギの着ぐるみで差し替えたアニメ
ss_12

課題

今回作成したベジェ曲線環境ですが、ゲーム制作へ導入するには大きな課題が2つあります。

描画速度

とにかく画像の生成が遅いです(※端末の性能と解像度のバランスにもよりますが、1回の画像生成に数十ミリ秒かかっています)。
特に、大きなサイズでリアルタイムにアニメさせようとすると、処理落ちがひどいことになります。

描画速度の改善が課題となります。

一方で、1枚絵ジェネレータとして、アプリ起動時にキーフレーム別に画像を生成しておき、その後、静的に参照する使い方であれば問題なく導入できそうです。

データ作成用のエディタ

今回、サンプルデータの作成はデバッグ画面レベルの簡易エディタで行いました。
が、現段階では、とても人様に提供できるレベルではありません(使いづらい&動作が安定しない等)。

ゲーム制作においては、大量のデータをいかに効率よく作成できるかが重要です。
エディタの良し悪しで工数は大きく変わってくるので、使いやすいエディタの準備が課題となります。

最後に

さて、ベジェ曲線によるゲーム素材作りを試行錯誤してみましたが、一番手応えを感じたのがストローク描画の部分です。

例えば、ツインテールだけを20%ぐらいのサイズまで縮小した場合でも、アンカーポイントの座標がかわるだけで描かれる線の太さはかわらず、ほかのパーツと共存させても違和感がありませんでした(※ペイント形式データの場合、一部のパーツを20%に縮小しようものなら、主線等が潰れて絵的なニュアンスが変化してしまい、他のパーツとの間で見た目に違和感がでます)。
パーツ毎に派手な拡大&回転をしてしまっても他のパーツと馴染んでくれるのは、動的に線を描くベジェ曲線ならではの強みだと思います。

さらに、ストローク描画の際、1画素単位をプログラム側で管理できるので、そこで色々な表現を加えることも可能そうです。
例えば、細密画のように主線に沿ってタッチをつけたり、テクスチャブラシのような表現が考えられます。

アンカーポイントの補正を含め、ベジェ曲線によるゲーム作成には十分な可能性がありそうです。
少ない素材&手間によりお手軽に表現のバリエーションがだせるよう、模索していこうと思います。

おまけ

GitHubReadMe に掲載している、大きめな動作例の画像です。
よろしければご覧くださいませ(※一枚一枚のサイズが大きいので別窓で開きます)。

動作例1
動作例2
動作例3

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

jpeg画像を元にiOSの画像を@x2/@x3を用意する方法

<背景を透明にし、不要な背景部分をカットし、画像サイズを変える>

背景を透過にする->pngに変換される
https://qiita.com/na1412/items/90a9641d9644e1bfbb49

トリミング(不要な背景を削除)
http://www.mac-beginner.com/77.html

画像サイズを変える(解像度も変えられる)
https://www.fusenhonpo.com/submit/popup_mac.html
->@x2@x3が必要なので、@x3の「幅」を3/2の長さで指定してやれば良い

反転はプレビュー.appで行う。

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

画像サイズについて

<Navigation Drawerに表示するヘッダの画像サイズについて>
Navigation Drawerの幅は240〜320dpにすべき
https://qiita.com/nein37/items/6c57fe5ec94a7914872c

<画像サイズについて>
Androidにおいて、一般的なスマートフォンの横幅は320-384dp
xxxhdpi  640dpi以下で作成
xxhdpi   480dpi以下で作成
xhdpi   320dpi以下で作成
hdpi  240dpi以下で作成
mdpi    160dpi以下で作成

iOSにおいて、320pxはiPhone 3GS非Retinaの画面幅いっぱいのサイズ
->Retinaはそこから倍数だったが、途中から中途半端になってきた
(iPhone 4 ~ 5sまでは Retinaで = 640)
https://qiita.com/Yuta/items/98b9ea2739718b9184de
https://backapp.co.jp/blog/11573/#Android_dp_iOS_Retina
https://www.webtech.co.jp/blog/optpix_labs/6915/

<例:320dpの画像を作成依頼するとき>
幅320dpで指定。高さはお好み

1dp=
mdpi:1px
hdpi:1.5px
xhdpi:2px
xxhdpi:3px
xxxhdpi:4px

なので、横幅320dpの画像が欲しい場合は以下で作成する

320px x 高さ任意
->【Android】mdpiで使用【iOS】非Retina端末で使用
480px x 高さ任意
->【Android】hdpiで使用
640px x 高さ任意
->【Android】xhdpiで使用【iOS】@2xで使用
960px x 高さ任意
->【Android】xxhdpiで使用【iOS】@3xで使用
1280px x 高さ任意
->【Android】xxxhdpiで使用

<背景を透明にし、不要な背景部分をカットし、画像サイズを変える>

背景を透過にする->pngに変換される
https://qiita.com/na1412/items/90a9641d9644e1bfbb49

トリミング(不要な背景を削除)
http://www.mac-beginner.com/77.html

画像サイズを変える(解像度も変えられる)
https://www.fusenhonpo.com/submit/popup_mac.html
->@x2@x3が必要なので、@x3の「幅」を3/2の長さで指定してやれば良い

反転はプレビュー.appで行う。

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

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で続きを読む

UnityのiOSビルド時にBuild SettingsやBuild Phases等の項目を自動追加する方法

実装環境

  • Unity 2019.2.21f1
  • Xcode 11.6

参考サイト

https://stackoverflow.com/questions/56479894/unity-3d-postprocessbuild-how-to-add-new-run-script-to-pbxproject-from-unity

方法

  1. 参考サイト記載のサンプルプログラムをダウンロードし、'Assets/Editor'フォルダ下に設置

  2. Users/UserOnPostBuild.cs内のEditProjメソッドを適宜書き換える

PostXcodeBuild.cs
    private static void EditProj(string pathToBuiltProject)
    {
            // 項目追加に必要な情報を取得(必須)
            var projPath = pathToBuiltProject + "/Unity-iPhone.xcodeproj/project.pbxproj";
            var pbxProj = new Users.Custom.PBXProject();
            pbxProj.ReadFromFile(projPath);
            var targetGuid = pbxProj.TargetGuidByName("Unity-iPhone");

            // BuildPhasesの追加サンプル
            // 不要なら削除
            pbxProj.AppendShellScriptBuildPhase(targetGuid,"Run Script copy_test.sh","/bin/sh","./../Assets/Editor/copy_test.sh");

            // BuildSettingsの追加処理サンプル
            // 不要なら削除
            pbxProj.SetBuildProperty(targetGuid, "IPHONEOS_DEPLOYMENT_TARGET", "9.0");

            // Frameworkの追加処理サンプル
            // 不要なら削除
            pbxProj.AddFrameworkToProject(targetGuid, "CoreBluetooth.framework", true);

            // embedded frameworkの追加サンプル
            // 不要なら削除
            var defaultLocationInProj = "Frameworks/Plugins/iOS/EmbeddedFramework/";
            var relativeCoreFrameworkPath = "";
            string[] commonFrameworkNames=new string[]{"EmbeddedFramework1","EmbeddedFramework2"};
            foreach (var frameworkNameTemp in commonFrameworkNames) {
                var frameworkName = frameworkNameTemp+".framework";
                 relativeCoreFrameworkPath = Path.Combine(defaultLocationInProj, frameworkName);
                AddDynamicFrameworks (ref pbxProj,targetGuid,relativeCoreFrameworkPath);
            }

            // .dylibの追加サンプル
            // 不要なら削除
            pbxProj.AddFileToBuild(targetGuid, pbxProj.AddFile("usr/lib/libresolv.dylib", "Frameworks/libresolv.dylib", Users.Custom.PBXSourceTree.Sdk));

            // 追加処理を書き込む(必須)
            pbxProj.WriteToFile(projPath);
        }
  • このエントリーをはてなブックマークに追加
  • 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で続きを読む