20201015のSwiftに関する記事は10件です。

Swiftでcosxの近似値を求める

なぜSwiftなの?

Swiftは環境構築などする必要がなく,MacかiPadがあればPlaygroundsをインストールするだけで使うことができる。
また,Playgroundsにはステップ実行ゆっくり実行という機能があり,for文やwhile分をステップごとに実行し,実行されている時の値の変化を確認することもできる。
さらに,ビューア機能を使えば変数の変化の様子を勝手にグラフ表示してくれる。
2020-10-14 11.16のイメージ.jpg

cosx のマクローリン展開

$cosx$をマクローリン展開すると,

$$
cosx=1-\frac{x^2}{2!}+\frac{x^4}{4!}-⋯+\frac{(-1)^nx^{2n}}{(2n)!}+⋯
$$
と表されるのであった。この記事では$sinx$の近似値をプログラミングを使って求めていく。(言語はSwift)ipadでも大丈夫。

1.変数を用意する

マクローリン展開は何回でも微分できる関数$f(x)$を$x$の冪乗関数の級数の展開式の和で近似させるものであるのであった。この記事では$sinx$の値をsumとして,sumにだんだん値を加えていく。

var sum = 0
print(sum)    //0
sum += 1
print(sum)    //1

また,近似する$x$を入れる定数も用意しなければならない。この時近似する値として$\pi/6$や$\pi/3$などの値を用いたいので,$\pi$が使えるようimport Foundationを書いておく。

import Foundation
var sum = 0
let x = Double.pi/2

さらに,項として更新され続ける変数を用意する。

var item = x

第一項は定数と同じになるので,xを入れておく。

最終的なコードは以下のようになる。

import Foundation
var sum = 0.0  //int型とdouble型は足算できないので,0.0と書きdouble型にする。
let x = Double.pi/2
var item = x

2.x=π/2として,第二項までコーディングする

コーディングをする上で小さな値から始めることや具体的な値で試行錯誤することは重要だと思う。(個人の見解)。この記事でもその理念に則り$x=\frac{\pi}{2}$として第二項まで,つまり

$$
cosx=1-\frac{x^2}{2!}
$$
までコーディングする。

import Foundation
var sum = 0.0  //int型とdouble型は足算できないので,0.0と書きdouble型にする。
let x = Double.pi/2
var item = 1

sum += 1
sum += -(x*x)/Double(2*1)

sumに$1$を加え,次の行で$-\frac{x^2}{2!}$を加えている。

最後に真値と比較するprint文を加える。

import Foundation
var sum = 0.0  //int型とdouble型は足算できないので,0.0と書きdouble型にする。
var x = Double.pi/2

sum += 1
sum += -(x*x*x)/Double(3*2*1)

print(sum)//近似値0.9248322292886504
print(sin(Double.pi/2))//真値1.0

Double(2*1)としているのはInt型をDoule型に変換しているのだが,この記事では本質的な部分ではないので説明は割愛。

3.一般化しやすいように工夫する

さっきは第二項を$-\frac{x^2}{2!}$としたが,これでは項数が増えるたびに$x$を増やさなければならない。項数が増えても短くコーディングすることができるように工夫を加える。

import Foundation
var sum = 0.0  //int型とdouble型は足算できないので,0.0と書きdouble型にする。
let x = Double.pi/3
var item = 1

sum += item

for i in 1 ..< 3{
    item *= -(x*x)/Double((2*i)*(2*i-1))
    sum += item
}

print(sum)//近似値0.525479753410744
print(cos(Double.pi/3))//真値0.5

4.項数をふやす

一般化することによって項数を増やしても対応できる。

import Foundation
var sum = 0.0  //int型とdouble型は足算できないので,0.0と書きdouble型にする。
let x = Double.pi/3
var item = 1.0

sum += item

for i in 1 ..< 6{
    item *= -(x*x)/Double((2*i)*(2*i-1))
    sum += item
}

print(sum)//近似値0.499999999639
print(cos(Double.pi/3))//真値0.5

第6項までの近似,つまり

$$
cosx=1-\frac{x^2}{2!}+\frac{x^4}{4!}-\frac{x^6}{6!}+\frac{x^8}{8!}-\frac{x^{10}}{10!}
$$
ではほとんど誤差が無いことがわかる。

おまけ

一般化するときは表にすると捉えやすい。

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

元自衛官実務未経験から受託開発企業から内定もらうまで

今回の記事はタイトル通り実務未経験から企業から内定もらうまで私がどんなことをしたのかを紹介します。
予め言いますがこれが私個人の経験からですのであくまで参考に、、、

大まかな筋道

海軍将校になりたくて海自入る->こんな仕事もう嫌ァァァ

辞めた

なんか稼げる仕事ないかな、、、(Youtube漁る)

プログラミングって結構稼げるんだ
とりあえずエンジニア界隈でイケてたKB○Yさんを見習ってFlutterを独学で勉強

挫折

プログラミングスクール通う
なんとなくiOSコースを受講
割とできそうだった

友達の大学生は就活真っ最中で自分も仕事したいと焦る
勉強会やスクールのメンターと話たりしてどんな方法が稼ぐのに最適か調べた
エンジニアとして一番稼げるのは個人で案件を獲得するいわゆるフリーランサー
じゃあiOSの案件ってどんなのあるの
実務経験者募集がほとんど。応募してみてもやっぱダメみたい。
自分がどんなことできるのかアピールできるものが必要->ポートフォリオ
これを宣伝してアピールできないかな

ムリだった、、笑
じゃあ仕事するか。技術磨いてまた挑戦しよう。1ヶ月間だけ就活しようムリなら東京出て頭下げまくってお願いしよう

ここでも実務経験が必要なとこがほとんど。
エージェントやポートフォリオ作っても書類選考も通れない
通れても御堅い企業やSlerとか。もっと自由な働き方したい

Indeedでたまたま上にでできた企業に応募。
とりあえず面接へ。
良さげな雰囲気、後日面接。なんとその後会食がしたいと連絡。
7割受かったと喜びいざ当日。面接というか企業情報を聞いただけその後焼肉奢ってもらった。
無事内定。

実際こんな感じ

よくこの職場で働きたいからこの言語で勉強みてみようって目にしますが、実際自分の行きたい会社に入れるのなんて奇跡に近いってか奇跡w
全てタイミングと運でしかなく就活中は「本当に会社行けんのか」って不安でしかたなかった。
先が見えない違業界からの転職ってこんな大変なんだって思い知らされましたね、、、
でも絶対どっか雇ってくれるところはあるし、技術がなくてもなんとかなるって精神はほんとに大事でしたね。

毎日4通は違う企業にメールしてたしフレームはあったけどちゃんと毎回違う志望動機でした。(簡単そうに見えて4つ書くのに企業研究含め3、4時間かけてましたね?)
送っても返事は帰って来ないことがほとんどだし心折れかけましたw

こんなことしてみては、、、?

私の失敗からこれからエンジニアを目指す方に聞いて欲しいのは、なぜエンジニアを目指しているのか明確にすることですね。
まずprogateとかオンラインスクールに入る前に考えて欲しいです。
エンジニアになって何がしたいのか、どんなエンジニアになっていたいか、本当にエンジニアになりたいのか。など

私がエンジニアを目指したのは、この職業が稼げると思ったからです。
実際に稼いでいる方はいますし情報社会で職業がなくなることはほぼないです。
勉強会やエンジニアの方とお話しした中で感じたのは、エンジニアとして稼ぐにはプログラムと〇〇です。
この〇〇はマーケティングや営業、企画や人脈、、、など様々。
どんなものでもどんな人でもチャンスは巡ってます。
でもどんな才能があってめちゃくちゃプログラミングができてもコミュ障じゃ売れないんです。

何が言いたいかというと目標がないとやっていけません。
あの人になりたいとかこれがしたいからとかも大事ですが、一番大事なのは「この夢はプログラミングがないとできないわ」って明確な目標。
これめちゃくちゃ難しいです。
でもこの目標があれば面接で強いし挫けかけても立ち直れると思います。
僕はこれを考えるまで苦労しました。

最後に

これをみているみなさんがなってよかったと思えるエンジニアになれるよう願っています。
手を動かすのも大事ですが、休憩も大事です。
かのワンピースのミホークも
B-sQQnNW0AEy8kz.jpg
って言ってます。
休みながらやっていきましょう。。。。


めんどくさくて確認してないので誤字脱字や脈絡のない文章であるかと思いますが、そっとコメントで教えてあげてください。
もしくは、見て見ぬふりでお願いします( ˘ω˘)スヤァ


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

UIAlertViewControllerをpresentで表示中はUINavigationControllerのスタックを更新できない

複数のUIAlertViewControllerがキューイングされたうえで同時にpresentされるようなイベントがあり、その時に表題の仕様を知らずハマりました。

ハマった一例

    // ①イベントの例外処理等で呼ばれるアラート
    func showCancelOperationDialog() {

        let alertController = UIAlertController(title: "title", message: "message", preferredStyle: .alert)
        // OKアクション
        let defaultAction: UIAlertAction = UIAlertAction(title: "OK", style: .default, handler: { _ in

            self.moveToFirstViewController()
        })
        // キャンセルアクション
        let cancelAction: UIAlertAction = UIAlertAction(title: "キャンセル", style: .cancel, handler: nil)
        alertController.addAction(cancelAction)
        alertController.addAction(defaultAction)
        present(alertController, animated: true, completion: nil)
    }

    // ②特定のエラーで呼ばれるアラート
    func showExceptionErrorDialog() {

        let alertController = UIAlertController(title: "title", message: "message", preferredStyle: .alert)
        // OKアクション
        let defaultAction: UIAlertAction = UIAlertAction(title: "OK", style: .default, handler: nil)
        // キャンセルアクション
        let cancelAction: UIAlertAction = UIAlertAction(title: "キャンセル", style: .cancel, handler: nil)
        alertController.addAction(cancelAction)
        alertController.addAction(defaultAction)
        present(alertController, animated: true, completion: nil)
    }

    // 特定のViewControllerへ戻る処理
    func moveToFirstViewController() {

        guard let firstViewController = self.navigationController?.viewControllers.first as? FirstViewController else {

            return
        }
        self.navigationController?.popToViewController(firstViewController, animated: true)
    }

上記のアラート2種が同時に呼び出され、①イベントの例外処理等で呼ばれるアラート -> ②特定のエラーで呼ばれるアラートの順で実行された場合、①のOKアクションでpopToViewControllerが行われません。正確には呼び出しされているのですが表題の通り、presentで表示中はUINavigationControllerのスタックを更新できないため、画面遷移が行われません。

回避策

任意のViewControllerに遷移してからアラートを表示させるようにしました。
例外処理としての画面遷移なので遷移 -> アラート表示の順が好ましいですね。

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

TableViewをスクロールしてデータ更新(上・下それぞれバージョン)

SNSなどで見かける、読み込み済みコンテンツの端までいったら、続きを読み込む方法です。
IMG_1523.PNG

データ更新部分を準備

データ更新処理を待つためにセマフォを設定します。

let semaphore = DispatchSemaphore(value: 1)

リフレッシュ時に以下の関数を呼ぶこととします。
ここで通信リクエストなど行います。

func updateData () {
    DispatchQueue.global().async {

        //*** ここでデータを更新する処理をする ***

        DispatchQueue.main.async {
            self.tableView.reloadData()
            self.semaphore.signal() // 処理が終わった信号を送る
        }
    }
}

下向きスクロールで更新

下向きに引っ張ってリフレッシュする場合は、UIRefreshControlが使えます。

この記事:[Swift/iOS] フリックで更新するTableViewの方法です。

1、UIRefreshControlを設置します

var refreshControl:UIRefreshControl!
override func viewDidLoad() {
        super.viewDidLoad()
        refreshControl = UIRefreshControl()
        refreshControl.attributedTitle = NSAttributedString(string: "再読み込み中")
        refreshControl.addTarget(self, action: #selector(ViewController.refresh), for: UIControl.Event.valueChanged)
        tableView.addSubview(refreshControl)
}

2、リフレッシュ時に呼ばれる関数を設定します

    @objc func refresh() {
        updateData()

        semaphore.wait()
        semaphore.signal()
        // データ更新関数が終了したら、リフレッシュの表示も終了する
        refreshControl.endRefreshing()
    }

上向きスクロールで更新

TableViewの一番下までスクロールしたら続きを更新する方法。YoutubeなどのUIみたいな。
ScrollViewのデリゲートメソッドを使います。TableViewはScrollViewクラスを継承しているので。

func scrollViewDidScroll(_ scrollView: UIScrollView) {
   let height = scrollView.frame.size.height
   let contentYoffset = scrollView.contentOffset.y
   let distanceFromBottom = scrollView.contentSize.height - contentYoffset
   if distanceFromBottom < height {
       if self.reloading { // デリゲートは何回も呼ばれてしまうので、リロード中はfalseにしておく
           updateData()
           semaphore.wait()
           semaphore.signal()        
           self.reloading = false
       }
   }
}

?


お仕事のご相談こちらまで
rockyshikoku@gmail.com

Core MLを使ったアプリを作っています。
機械学習関連の情報を発信しています。

Twitter
Medium

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

Youtube Data APIのリクエスト結果表示数(resultsPerPage)を変える

デフォルトでは「5」だけ表示されます。

"pageInfo": {
    "totalResults": 40,
    "resultsPerPage": 5
}

次の5個を取得するには、結果に含まれる「NextPageToken」をリクエストURLにつけます。

https://www.googleapis.com/youtube/v3/playlists?part=snippet&channelId={CHANNEL_ID}&key={API_KEY}&pageToken={PAGE_TOKEN}

最初から取得する数を増やすには、「max_Results」をリクエストURLにつけます。

https://www.googleapis.com/youtube/v3/playlists?part=snippet&channelId={CHANNEL_ID}&key={API_KEY}&&maxResults=20
"pageInfo": {
    "totalResults": 40,
    "resultsPerPage": 20
}

?


お仕事のご相談こちらまで
rockyshikoku@gmail.com

Core MLを使ったアプリを作っています。
機械学習関連の情報を発信しています。

Twitter
Medium

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

ARKit + Metalで手をゆらゆらさせてみる

ARKitとMetalを使って手をゆらゆらさせる方法を紹介します。

仕上がりは、こんな感じです。
20201015_qiita用_ショート.gif

考え方

ARKitのPeople Occlusionを使用します。

People Occlusionを使用すると人型のマスクテクスチャが得られるので、そのマスクとカメラの画像を同時に歪ませて、歪んだ合成用画像を取得します。歪んだまま切り取る感じです。

こんな画像が取得できます(わかりやすくするために動画よりも強めに歪ませています)

この歪ませた手の映像と、元の映像を重ねることで上のような動画を作ることができます。

手順

1.Appleの公式サンプルを入手する

Appleの公式サンプルにPeople Occlusionを使用して人型のマスクテクスチャを得る処理が書かれているため、これをベースに書いていきます。

Effecting People Occlusion in Custom Renderers

2. シェーダーに経過時間を渡す

ゆらゆらさせるには、経過時間を変形式に与える必要があります。

シェーダーに渡すstructを宣言します。

Renderer.swift
struct Uniforms {
    var time: Float = 0
}

次に、経過時間を管理するための変数と、開始時間を宣言します。

Renderer.swift
class Renderer {
    var uniforms = Uniforms()
    private var startDate: Date = Date()
    var uniformsBuffer: MTLBuffer! // シェーダに渡すためのバッファ

そしてシェーダに情報を渡しているcompositeImagesWithEncoderメソッドの中で一緒に経過時間を渡してあげます。

Renderer.swift
uniforms.time = time
uniformsBuffer = device.makeBuffer(bytes: &uniforms, length: MemoryLayout<Uniforms>.stride, options: [])
uniformsBuffer.label = "UniformsBuffer"

シェーダー側でもSwift側と同じstructを用意しておき、関数の引数として受け取ります。
引数名はmyUniformsとしています。

Shaders.metal
struct Uniforms {
    float time;
};

fragment half4 compositeImageFragmentShader(CompositeColorInOut in [[ stage_in ]],
                                    texture2d<float, access::sample> capturedImageTextureY [[ texture(0) ]],
                                    texture2d<float, access::sample> capturedImageTextureCbCr [[ texture(1) ]],
                                    texture2d<float, access::sample> sceneColorTexture [[ texture(2) ]],
                                    depth2d<float, access::sample> sceneDepthTexture [[ texture(3) ]],
                                    texture2d<float, access::sample> alphaTexture [[ texture(4) ]],
                                    texture2d<float, access::sample> dilatedDepthTexture [[ texture(5) ]],
                                    constant SharedUniforms &uniforms [[ buffer(kBufferIndexSharedUniforms) ]],
                                    constant Uniforms &myUniforms [[buffer(kBufferIndexMyUniforms)]])
{

3. シェーダーを書き換える

公式サンプルのうち、Shaders.metalのcompositeImageFragmentShader関数を次のように書き換えます。

Shaders.metal
@@ -219,8 +397,9 @@ fragment half4 compositeImageFragmentShader(CompositeColorInOut in [[ stage_in ]
     half4 sceneColor = half4(sceneColorTexture.sample(s, sceneTexCoord));
     float sceneDepth = sceneDepthTexture.sample(s, sceneTexCoord);

+    float2 modifier = float2(sin(cameraTexCoord.y + myUniforms.time*5)*0.2, 0); // 変形の式
     half4 cameraColor = half4(rgb);
-    half alpha = half(alphaTexture.sample(s, cameraTexCoord).r);
+    half alpha = half(alphaTexture.sample(s, cameraTexCoord + modifier).r); // 人型マスクを変形させる

     half showOccluder = 1.0;

@@ -233,8 +412,11 @@ fragment half4 compositeImageFragmentShader(CompositeColorInOut in [[ stage_in ]
         showOccluder = (half)step(dilatedDepth, sceneDepth); // forwardZ case
     }

+    float2 displacedUV = sceneTexCoord + modifier; // 画像を変形させる

-    half4 occluderResult = mix(sceneColor, cameraColor, alpha);
+    half4 displacedCol = half4(sceneColorTexture.sample(s, displacedUV)); // 変形した人型画像の取得
+    half4 occluderResult = mix(sceneColor, displacedCol, alpha); // 変形した画像と元の画像を合成
     half4 mattingResult = mix(sceneColor, occluderResult, showOccluder);
     return mattingResult;
 }

重要なのはここです。

Shaders.metal
float2 modifier = float2(sin(cameraTexCoord.y + myUniforms.time*5)*0.2, 0); // 変形の式

sin関数に入力画像のy座標と、経過時間を足したものを与え、これをmodifyerに代入しています。
modifierはカメラや人型マスクに加算するための変数で、今回はxだけに式が入っているので、x軸方向だけがゆらめくことになります。

なお、実際の動画は縦方向にゆらいでいるので、上の式と矛盾しますがこれは、iPhoneをランドスケープの状態で録画したものを、画像編集ソフトで縦に変換したためです。

図形の変形については、こちらの記事に書きましたので、こちらもご覧ください。

ARKitやSceneKitの図形をMetalシェーダーで変形させる方法

仕上がり

Youtubeにも動画をアップしています。
仕上がりの動画(Youtube)

ソースコードはこちらです。

最後に

NoteではiOS開発について定期的に発信していますので、フォローしていただけますと幸いです。
https://note.com/tokyoyoshida

Twitterでは簡単なtipsを発信しています。
https://twitter.com/jugemjugemjugem

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

iOS14で動画再生中、バックグラウンド移行時に一時停止されてしまう問題への対応

対応

  • playerView.player = nil
  • playerView.player = player

の実行タイミングを変更する

@objc func applicationWillResignActive(_ notification: NSNotification?) {
   playerView.player = nil
}

@objc func applicationDidBecomeActive(_ notification: NSNotification?) {
   playerView.player = player
}

経緯

  • ユーザー「iOS14にアップデートしたらバックグラウンド再生がおかしい。なんとかしろ:rage::bangbang:
  • ワタクシ「:sob:

対応前

  • applicationDidEnterBackgroundplayerView.player = nil していた ※Appleさんの公開しているドキュメントの記載内容に従ったもの

調査

  • 何もしてないのに動かなくなった ⇒ OSの仕様変わったな?
  • バックグラウンドに行ったら一時停止 ⇒ layerにplayer設定したままの動作と同じだな?

検証

  1. 動画再生中、バックグラウンド移行前に、手動で playerView.player = nil する
  2. バックグラウンドへ移行する

「再生が継続された!」

導き出される結論は…
「layer に player があるかどうかチェックしているタイミング変わったんじゃね?」

再検証

  1. applicationDidEnterBackground より前に実行される applicationWillResignActiveplayerView.player = nil するよう変更
  2. applicationWillResignActiveに対となるのが applicationDidBecomeActive となるため、そこで playerView.player = player するよう変更

「再生が継続された!」

※iOS11-14、iPadOS13,14で動作することも確認しました

まとめ

  • Appleさんのドキュメントのままに書いても動作しないため、OS不具合の可能性が高いです
    • iOS 14.2 beta2 で、この不具合が修正されているとの噂です

その他

  • iOS14がリリースされてしばらく経ちましたが、この対応方法を見かけなかったため記事を投稿します

参考文献

  1. Apple : Playing media while in the background using AV Foundation on iOS
  2. Apple : Playing Audio from a Video Asset in the Background
  3. @KenNagami : iOSアプリのライフサイクル
  4. @bosteri_bon : iOS14で「バックグラウンド再生」が出来ない不具合への対処
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

iOS14のSwiftUIではリストのスクロール処理をコードで制御できるようになった

はじめに

iOS13のSwiftUIではできなかったコードによるリストのスクロール処理が、iOS14ではできるようになりました。
本記事でその実装方法をまとめています。

iOS13でのスクロール処理

iOSアプリを開発しているとよくある「○番目のセルに自動でスクロールする」という処理ですが、
これをiOS13のSwiftUIでは実現する方法がありませんでした。

struct ContentView: View {
    var body: some View {
        List(0..<100) {
            Text("\($0)")
        }
    }
}

UIKitでは以下のような形で、scrollToRow, scrollToItemなどのメソッドを呼び出す形で該当の要件を簡単に実装できるので、
この機能のためだけにUIViewRepresentable, UIViewControllerRepresentableなどを利用することもしばしばありました。

// UITableView
tableView.scrollToRow(at: IndexPath(row: 10, section: 0),
                      at: .top,
                      animated: true)

// UICollectionView
collectionView.scrollToItem(at: .init(item: 10, section: 0), 
                            at: .top,
                            animated: true)

iOS14でのスクロール処理

iOS14では ScrollViewReader というスクロール状態を制御できる新しいViewが追加されており、
これを利用すると任意の地点に自動でスクロールさせる処理の実装が可能になります。

以下にサンプルコードを示します。

準備

まずは、準備としてスクロール地点を指定するためのTextFieldを設置します。

struct ContentView: View {
    @State private var text: String = ""

    var body: some View {
        VStack {
            HStack {
                TextField("input row number", text: $text)
                Button("Scroll") {
                    guard let row = Int(text) else { return }
                    print(row)
                }
            }.padding()

            List(0..<100) {
                Text("\($0)")
            }
        }
    }
}

これにより、ボタンをタップするとユーザーがTextFieldに入力した値を取得できるようになります。

スクロール制御

それでは実際にスクロールさせる処理の実装部分です。

まずはスクロール制御をしたい箇所を ScrollViewReader で囲います。
これによりスクロール制御が可能な ScrollViewProxy インスタンスの取得ができるようになります。

struct ContentView: View {
    @State private var text: String = ""

    var body: some View {
        VStack {
+           ScrollViewReader { (proxy: ScrollViewProxy) in
                HStack {
                    TextField("input row number", text: $text)
                    Button("Scroll") {
                        guard let row = Int(text) else { return }
                    }
                }.padding()
                List(0..<100) {
                    Text("\($0)")
                }
+           }
        }
    }
}

さらに、スクロール位置を特定するためのIDを対象のViewに対して付与しておき、
制御を開始したい部分で ScrollViewProxyscrollTo メソッドにそのIDを指定するだけでスクロール処理を実現することができます。

struct ContentView: View {
    @State private var text: String = ""

    var body: some View {
        VStack {
            ScrollViewReader { (proxy: ScrollViewProxy) in
                HStack {
                    TextField("input row number", text: $text)
                    Button("Scroll") {
                        guard let row = Int(text) else { return }
+                       withAnimation {
+                           proxy.scrollTo(row, anchor: .top)
+                       }
                    }
                }.padding()

                List(0..<100) {
                    Text("\($0)")
+                       .id($0)
                }
            }
        }
    }
}

これでやりたいことが実現できるようになりました。

ちなみに withAnimation を付与しなければ、スクロールのアニメーションは行われなくなります。
また、第二引数の anchor でスクロール後の位置を細かく指定できます。

おまけ

Gridに対しても問題なく動作しました。

struct ContentView: View {
    @State private var text: String = ""

    var body: some View {
        VStack {
            ScrollViewReader { (proxy: ScrollViewProxy) in
                HStack {
                    TextField("input row number", text: $text)
                    Button("Scroll") {
                        guard let row = Int(text) else { return }
                        withAnimation {
                            proxy.scrollTo(row, anchor: .top)
                        }
                    }
                }.padding()

                ScrollView {
                    LazyVGrid(
                        columns: [
                            GridItem(.flexible(minimum: 0, maximum: .infinity)),
                            GridItem(.flexible(minimum: 0, maximum: .infinity)),
                        ],
                        alignment: .center,
                        spacing: nil
                    ) {
                        ForEach(0..<100) {
                            Text("\($0)")
                                .frame(height: 100)
                        }
                    }
                }
            }
        }
    }
}

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

Cookiecutterを使って爆速でiOSプロジェクトを作成する

cookiecutter_medium.png

はじめに

あなたは開発スタート時に毎回新しいXcodeプロジェクトを作っていませんか?
過去のプロジェクトから使ってもいいヤツらを 「Add Files to」していませんか?
xcconfigやcarthageの設定を毎回行っていませんか?

そうです、まさに私のことです。

というわけで、開発初期のスピードを加速させるべく
Cookiecutter というPythonのツールを利用して コマンド一発
iOSプロジェクトを作成している例があったので真似してみることにしました。

クイックスタート

サンプルとしてCleanArchitectureでの開発用テンプレートを作成してみました。
https://github.com/SatoshiN303/iOS-Blueprints-CleanArchitecture

$ brew install cookiecutter
$ cookiecutter https://github.com/SatoshiN303/iOS-Blueprints-CleanArchitecture.git

上記のコマンドを叩くと 「アプリ名」 「会社名」 「バンドルID」が聞かれます。
それぞれを入力するとプロジェクト作成完了です。
スクリーンショット 2020-10-14 18.35.49.png

生成されるサンプルプロジェクトについて

  • bootstrap.sh
  • .xcconfig (debug/adhoc/release)
  • CocoaPods Settings
  • Carthage Setting
  • no Main.storyboard
  • DependencyInjection SampleCode
  • Github's Repositry search process SampleCode

テンプレートの基本的な内容として上記を設定してみました。
普段通りに実装したものをCookiecutterテンプレートとして転用できるのは便利です。

ちなみに開発時には削除することになりますが
GitHubAPIを叩いて画面に表示するサンプルコードを追加しています。
理由としてCleanArchitectureで誰が何の責務を持っているか把握する為になります。
スクリーンショット 2020-10-15 0.03.10.png

Cookiecutterについて

主に機械学習向けのPythonパッケージやDjango等の
プロジェクトテンプレートを作成することができるツールです。

ドキュメントはこちらです。

cookiecutter-template を確認すると様々なテンプレートが公開されています。
まずは Forkして カスタマイズするのも有かなと思います。

Android向けiOS向けもそれなりにありました。
ちなみにローカルでも利用可能でした。

# Create project in the current working directory, from the local
# cookiecutter-pypackage/ template
$ cookiecutter cookiecutter-pypackage/

補足: Xcodeのプロジェクトテンプレートを使わなかった理由

Xcodeのオレオレプロジェクトテンプレート作成も試みたのですが
以下の理由で色々と面倒だったので cookiecutter を使う感じになりました。

  • TemplateInfo.plist をガッツリ弄るのが億劫
  • xcconfig とか BuildSettingsも設定したい

Xcodeの自作プロジェクトテンプレートに詳しい方などいらっしゃいましたら
コメント頂けると幸いでございます?‍♂️

今後やりたいこと

  • 再利用できるものはEmbedded Framework化した上でテンプレートに含める
  • 他のアーキテクチャも作りたい
  • テンプレートプロジェクト自体をCIで回してテンプレートの形骸化を防ぎたい
  • シェルスクリプト使ってAWS Amplifyも合わせて設定する

まとめ

Cookiecutter向けに自分好みのテンプレートを作って開発初期スピードを加速させよう

参考

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

XCode12でビルド先をシミュレーターにするとビルドできない!

ビルド先をシミュレーターにするとビルドできない!

XCode12からビルドシステムが変わったみたいで、
building for iOS Simulator, but linking in object file built for iOS, for architecture arm64
エラーが発生し、ビルドできないプロジェクトがあるみたいです。

解決方法

arm64をArchitecturesからはずせばいいらしい。

Build Settings > Architectures > Excluded Architectures > Any iOS Simulator SDK = arm64

CocoaPodsの設定

CocoaPodsのライブラリでエラーが出ている場合は、Podfileに↓を追記する。

Podfile
post_install do |installer|
  installer.pods_project.build_configurations.each do |config|
    if config.name == "Develop"
      config.build_settings['EXCLUDED_ARCHS[sdk=iphonesimulator*]'] = 'arm64'
    end
  end
end
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む