カテゴリー: 未分類

  • Amplify Studio を使う

    Amplify Studio を使う

    この記事ではAmplify Studio の使い方について軽めに解説します。

    amplify を使ってデプロイしたアプリに管理画面を作ろうとしたところ、古い記事を参照したため Amplify admin UI という単語が出てきたのですが、Amplify Studio に統合されたようです。

    Amplify Studio を開く

    始める前にGithubで新しくリポジトリを作っておくと便利です。

    amplify cli を使ってプロジェクトを準備する方法は下の記事をご覧ください。

    AWS console から Amplify を開きます。

    開いて、まだ自分のアプリをデプロイしていない場合は指示に従ってデプロイをしましょう。

    デプロイに成功すると、Amplify console から amplify studio の設定を開けるようになります。

    amplify console はターミナル上で以下コマンドを実行すると表示できます。 ⚠️検索バーでamplify と調べて出てくるページとは違うので注意

    amplify console

    console が開いたら Go to Amplify Studio to enable をクリックします。

    amplify studio を有効にします。 のトグルスイッチをオンにしましょう。

    読み込みが終わると上のような画面になります。Backend Environments のURLをクリックすると、Amplify Studio に移動できます。

    Login with AWS account のボタンを押して遷移しましょう。

    このような画面に遷移するはずです!

    ここまできたら、サイドバーからUI Library → sync with Figma など話題の機能が使えます!

    お疲れ様でした。

  • iMac の復旧ができなくなった “mac インストールの準備中にエラーが起きました”

    iMac の復旧ができなくなった “mac インストールの準備中にエラーが起きました”

    先日 AppliSilicon M1 iMac を譲り受けました。
    その際初期化したところ、OSのインストール時にエラーが起きてしまい、起動できなくなってしました。

    今回はそれの解決方法の記事です。

    環境

    壊れた方の iMac

    • Apple Silicon M1
    • Ventura
    • 16GB

    回復に使用した MacBook Air

    • Apple Silicon M1
    • Sequoia 15.0.1
    • 16GB

    問題の起きた場面と、エラー画面

    [macOS Ventura を再インストール]のボタンを押して、インストール画面に進むと以下のようなエラー画面が出てしまっていました。

    mac インストールの準備中にエラーが起きました

    このポップアップが出て、OSのインストールが完了しませんでした。何度やっても同じなのと、ディスクユーティリティで First Aid や削除も試しましたが、永遠に同じエラーが出続けました。

    画像撮ってなかったので、知恵袋で同じような悩みの方の画像引用させていただきます。
    https://detail.chiebukuro.yahoo.co.jp/qa/question_detail/q14292903294

    Apple のサポートページにも同じ症状の方がいました。

    https://discussionsjapan.apple.com/thread/255485243?sortBy=rank

    ちなみにエラーログは下記の通りです。ログからエラーの解決には繋がらなかったので画像のみで失礼します。

    error log

    解決した方法 DFU

    DFU(Device Firmware Update)とは、Appleデバイスのファームウェアをアップデートしたり修復したりするためのモードです。

    この方法は正常に動作している mac OS が手元にある場合にできる方法です。

    0. 壊れていない Mac を用意して Apple Configurator をインストールする

    私の場合 Apple Silicon M1 MacBook Air を使いました。

    この MacBook に Apple Configurator というアプリをインストールしておきます。

    https://apps.apple.com/jp/app/apple-configurator/id1037126344?mt=12

    1. ディスクユーティリティの操作

    iMac の復旧画面からディスクユーティリティを選択します。
    次にMachintosh HD の項目があるので、これを削除します。

    2. iMac(壊れている方) と MacBook(壊れていない) をつなぐ

    iMac と MacBook をケーブルで接続します。
    私は MacBook に付属していた C to C の充電ケーブルで動作を確認しました。

    3. iMac の電源コードを抜く

    iMac をシャットダウンして、電源コードを抜きます。

    4. DFUモードに入る

    電源ボタンを押しながら、電源ケーブルを指します。

    電源長押しではありません。私はずっと長押しでやるものだと勘違いしてました😭

    DFUモードに入ると iMac 側の画面は真っ暗なままですが、 MacBook Air (正常に動作している方)に [アクセサリの接続の許可]のダイアログが出てくるはずです!

    MacBook Air の Apple Configurator アプリ上では、以下のように DFU のアイコンが表示されるはずです。

    5. configurator から復旧する

    DFUアイコンの上から右クリックをします。
    そこから[復元する]という項目をクリックします。

    確認のポップアップが表示されるので、あとは[OK]を選択していけば大丈夫です!

    6. iMac でOS再インストールする

    この後 iMac が復旧するはずです!?
    私の環境では以下のように表示されました。

    mac os recovery

    画面の表示に従って再起動をすると

    セコイアインストールできた!

    [Reinstall macOS Sequoia]の文字が!!!

    これをする前までは[Reinstall macOS Ventura ]だったため、外部のmacから操作できたことが確認できました!

    参考

    Apple Configure2 ついて

    https://support.apple.com/ja-jp/guide/apple-configurator-2/apdd5f3c75ad/mac

    復旧についてのApple公式ページ

    https://support.apple.com/ja-jp/108900

  • Error: Failed to list Firebase projects. See firebase-debug.log for more info.

    Error: Failed to list Firebase projects. See firebase-debug.log for more info.

    これは何?

    firebase projects:list

    のコマンドを使うと下記のエラーが出てしまいました。これの解決のメモです。

    Error: Failed to list Firebase projects. See firebase-debug.log for more info.

    環境

    • AppleSilicon M1 MacBookAir
    • macOS
    • firebase cli version 13.23.0  

    解決方法

    再度ログインし直しましょう。
    firebase のバージョンアップ後にエラーが出ている場合があります。この場合、再度ログインをし直しましょう。

    firebase login --reauth

    私はこれで解決しました!

  • 【コラム】label systemimage を一瞬で探すために SF Symbols 5 をインストールしよう

    【コラム】label systemimage を一瞬で探すために SF Symbols 5 をインストールしよう

    注意 カスみたいなまとめ記事です 内容は無いです

    以下のURLから SF Symbols をダウンロードしましょう。

    https://developer.apple.com/sf-symbols

    こんな感じでネイティブアプリとして、systemimage を検索できるのでめっちゃ便利

    swiftUI でのコーディング中にアイコン付きのボタンを追加したい時がよくあると思いますが、
    SF Symbols のアプリを入れておけば直ぐに参照できます。

                    Button {
                        presented.toggle()
                    } label: {
                        Label("toggle inspector", systemImage: "info.circle")
                    }

    よく使う SF Symbols 紹介

    よく使うもので言うとこんな感じかな(メモを兼ねて幾つか紹介)

    info系
    info.circle は頻繁に使う

    ヘルプ機能には questionmark.circle

    色の変更などの編集系には

    paintbrush

    通知系の

    bell

    macOS なら絶対使う
    サイドバー関係のsymbol

    sidebar.right

  • 【mac os】画像の解像度変える最も簡単な方法

    【mac os】画像の解像度変える最も簡単な方法

    ❓ これは何

    mac os で画像の解像度を純正アプリのみで変更する方法です。

    🛠️ やり方

    Finderで画像を右クリックします。
    このアプリケーションで開く からプレビュー.app を選択します。

    画像が開いたら、右上の「プレビューで開く」ボタンを押します。

    開いたら、ツール/サイズを調整を選択します。

    できたら幅と高さの値を好きに変更してあげましょう。
    縦横比の固定を外すと好きにいじれます。

    このまま保存してもOKなのですが、
    元の写真を残したい場合は、
    ファイル/複製 を選択して保存すると編集したものもどちらも残ります。

    以上です!
    記事をご覧いただきありがとうございました。

  • https:// にリダイレクトしよう!

    https:// にリダイレクトしよう!

    ❓ これは何?

    この記事では http:// から https:// にリダイレクトする手順の紹介記事です。

    🖐️ やり方

    サーバーに ftp などで接続しましょう。

    ロリポップをお使いの方は公式のこのページを参照して、フォルダにアクセスできるような環境を整えましょう。

    .httaccess に下記のコードを追加しましょう。

    #Redirect http to https
    RewriteEngine On
    RewriteCond %{HTTPS} off
    RewriteRule ^ https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301]
    # end of redirect http to https

    🍪 何がいいの??

    httpsのメリットは主に以下の点でおすすめです。
    サイトを作ったら必ずhttps設定とリダイレクトの設定はしておいた方がいいでしょう。

    1. セキュリティ:HTTPSは、ユーザーとウェブサイト間の通信を暗号化します。これにより、第三者がデータを盗み見たり改ざんしたりするのを防ぎます。これは、特にクレジットカード情報やパスワードなどの機密情報を扱うウェブサイトにとって重要です。
    2. 信頼性:HTTPSを使用すると、ウェブサイトが本物であることを証明します。これにより、ユーザーは自分が訪れているサイトが信頼できるものであると確信できます。
    3. SEO(検索エンジン最適化):GoogleはHTTPSを使用するウェブサイトを優先してランク付けします。つまり、HTTPSを使用すると、ウェブサイトの検索エンジンの順位が向上する可能性があります。
    4. パフォーマンス:最新のHTTPSプロトコル(HTTP/2)は、従来のHTTPプロトコルよりも高速です。これにより、ウェブサイトの読み込み速度が向上します。

  • GitHub Actionsを使ったReactアプリのデプロイメント: ロリポップへのアップロード

    GitHub Actionsを使ったReactアプリのデプロイメント: ロリポップへのアップロード

    今回は、GitHub Actionsを使ってReactアプリをロリポップサーバーにデプロイする手順とその過程で遭遇した問題と解決方法を共有します。

    動作したコード

    詰まった点は後に書くとして、最初に動くコードを共有しますね!

    1, 最初にすること

    ReactアプリをGithubにプッシュ!

    2, Github側ですること

    SettingsタブからSecrets and variables を開いて、「New Repository Secret」を押す。
    ロリポップのアカウント情報/パスワード変更を開いて。それぞれ

    NameSecret
    FTP_HOSTFTPサーバー
    FTP_USERNAMEFTPアカウント
    FTP_PASSWORDFTPパスワード

    を入力します。

    3, コードを追加する

    今度は自分のコードを開いて

    ルートディレクトリに .github/worflows フォルダを作り、
    その中に lolipop.yml を作成する。

    name: Deploy React App to Lolipop
    
    on:
      push:
        branches:
          - main
    
    jobs:
      build:
        runs-on: ubuntu-latest
    
        steps:
          - name: Checkout repository
            uses: actions/checkout@v4
    
          - name: Set up Node.js
            uses: actions/setup-node@v4
            with:
              node-version: '20'
    
          - name: Cache dependencies
            uses: actions/cache@v4
            with:
              path: ~/.npm
              key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
              restore-keys: |
                ${{ runner.os }}-node-
    
          - name: Install dependencies
            run: npm install
            # yarn の場合は下の行参考
         # run: yarn install --frozen-lockfile
    
          - name: Build the React app
            run: |
              CI=false npm run build
    
          - name: Upload to Lolipop via FTP
            uses: SamKirkland/FTP-Deploy-Action@4.3.0
            with:
              server: ${{ secrets.FTP_HOST }}
              username: ${{ secrets.FTP_USERNAME }}
              password: ${{ secrets.FTP_PASSWORD }}
              # 自分のサイトのディレクトリを指定する
              server-dir: hoge/
              local-dir: build/
              protocol: ftps
    

    ここまで来れば、あとはpush すれば自動で反映されるはずです!
    お疲れ様でした!

    ここに辿り着くまで……。

    最初に試したコード

    lolipop.yml ファイルの中は以下の通りでした。

    name: Deploy React App to Lolipop
    
    on:
      push:
        branches:
          - main
    
    jobs:
      build:
        runs-on: ubuntu-latest
    
        steps:
          - name: Checkout repository
            uses: actions/checkout@v2
    
          - name: Set up Node.js
            uses: actions/setup-node@v2
            with:
              node-version: '16'
    
          - name: Install dependencies
            run: npm install
    
          - name: Build the React app
            run: npm run build
    
          - name: Upload to Lolipop via FTP
            uses: SamKirkland/FTP-Deploy-Action@4.3.0
            with:
              server: ${{ secrets.FTP_HOST }}
              username: ${{ secrets.FTP_USERNAME }}
              password: ${{ secrets.FTP_PASSWORD }}
              local-dir: build
              server-dir: /public_html/hoge

    問題1  Node Version の警告

    最初の問題は、Node.js 16のサポートが終了したため、actions/checkout@v2actions/setup-node@v2をNode.js 20に更新する必要があるという警告でした。

    Node.js 16 actions are deprecated. Please update the following actions to use Node.js 20: actions/checkout@v2, actions/setup-node@v2. For more information see: https://github.blog/changelog/2023-09-22-github-actions-transitioning-from-node-16-to-node-20/.

    警告の内容

    以下のように書き換えることでOK

    uses: actions/checkout@v4
    uses: actions/setup-node@v4
    with:
      node-version: '20'

    問題2 警告で処理がストップしてしまう

    今回のコードではお恥ずかしいことに、ESLintで警告が出ていました。
    しかし、今回は一旦アップロードしたい……。

    ということで、警告が出てもアップロードできるように以下のコードを追加しました。

    run: |
      CI=false npm run build

    問題3 ディレクトリのミス

    - name: Upload to Lolipop via FTP
            uses: SamKirkland/FTP-Deploy-Action@4.3.0
            with:
              server: ${{ secrets.FTP_HOST }}
              username: ${{ secrets.FTP_USERNAME }}
              password: ${{ secrets.FTP_PASSWORD }}
              server-dir: hoge/
              local-dir: build/
              protocol: ftps

    server-dir のところですが、必ず最後に”/”をつけるのを忘れずにしましょう。

    問題4 Actionsが遅すぎる

    Pushしてからアップロードされるまで時間がかかってしまいます。
    そのため、キャッシュの設定を追加しました。

    - name: Cache dependencies
            uses: actions/cache@v4
            with:
              path: ~/.npm
              key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
              restore-keys: |
                ${{ runner.os }}-node-

    終わりに

    GitHub Actionsを使用したReactアプリの自動デプロイメントの設定方法でした!
    以上です!この記事を読んでくださりありがとうございます!

    最後に参考貼っておきますね

    参考URL

    Github 公式

    https://docs.github.com/en/actions/creating-actions/metadata-syntax-for-github-actions#runs-for-javascript-actions

    ヘテムル

    https://tech.pepabo.com/2020/03/11/github-actions-for-lolipop-and-heteml

    ロリポップ公式Note

    https://note.com/mclolipopjp/n/n0e8dace1404b

  • pyenv で install 時にエラーが出てインストールできない

    pyenv で install 時にエラーが出てインストールできない

    環境

    MacBook Air M1, 2020
    MacOS 14.4.1

    困ったこと

    pyenv をhomebrew経由でinstallしました。
    pyenv –version でインストールを確認したものの、

    pyenv install 3.11.1

    と入力しても、途中でエラーが起きてしまいます。
    下はその時のログです。

    BUILD FAILED (OS X 14.4.1 using python-build 20180424)
    
    Inspect or clean up the working tree at /var/folders/v6/jcq_s0rd58x3kph7trv7jfjw0000gn/T/python-build.20240401212257.5503
    Results logged to /var/folders/v6/jcq_s0rd58x3kph7trv7jfjw0000gn/T/python-build.20240401212257.5503.log
    
    Last 10 log lines:
          __locale_localeconv in _localemodule.o
          __locale_localeconv in _localemodule.o
          __locale_localeconv in _localemodule.o
      "_libintl_textdomain", referenced from:
          __locale_textdomain in _localemodule.o
    ld: symbol(s) not found for architecture arm64
    clang: error: linker command failed with exit code 1 (use -v to see invocation)
    make: *** [Programs/_freeze_module] Error 1
    make: *** Waiting for unfinished jobs....
    2 warnings generated.
    

    解決した方法

    この記事の方法で解決しました。

    openssl, gettext, readline のアンインストール

    –ignore-dependenciesオプションで依存関係を無視してアンインストールすることができます。

    brew uninstall --ignore-dependencies openssl gettext readline

    openssl、gettext、readline の再インストール

    -x86_64 オプションは、特定のアーキテクチャでパッケージを再インストールするために使用されます。これにより、適切なアーキテクチャで再インストールされたことを確認できます。

     /usr/bin/arch -x86_64 brew install openssl gettext readline

    pyenv のインストール。

    ここでも-x86_64 オプションをつけています。

    $ /usr/bin/arch -x86_64 pyenv install 3.11.2

    これで成功しました。

    しかし、この後 $python –version では comand not found になってしまい、
    $python3 –versionでは設定ができるという状態になってしまいました。
    この問題に関しては以下の記事で解説しています。

    Mac $python と $python3 のバージョンを揃えたい

    ちなみに最初に再インストールしたパッケージは、
    opensslは、セキュリティ関連の機能を提供するライブラリ。

    gettextは、多言語対応のプログラムを作成するためのツールセットです。主な目的は、プログラム内のテキストメッセージを翻訳可能な形式で管理することです。多言語対応のアプリケーションやライブラリで、ユーザーインターフェースのテキストをローカライズする際に使用されます。

    readlineは、コマンドラインインターフェース(CLI)でのテキスト入力をサポートするライブラリです。主な機能は、ユーザーがコマンドラインで入力したテキストを編集、履歴を管理し、補完を提供することです。ターミナルやシェルでの対話的な入力を向上させるために使用されます。

    この3つともプログラミングやシステム管理のコンテキストで広く使用されています。

    他に試したダメだった方法

    pyenv の再インストール

    最初に試したものでしたが、上手くいきませんでした。

    $ brew uninstall pyenv
    $ brew install pyenv

    .zshrc, .zprofile に下記を書いたのを何度も確認しましたが、install 時のエラーとは関係ないようです。

    # pyenv settings
    export PYENV_ROOT="$HOME/.pyenv"
    command -v pyenv >/dev/null || export PATH="$PYENV_ROOT/bin:$PATH"
    eval "$(pyenv init -)"

    コマンドラインツールの再インストール

    以下のコードを実行することはどのブログにも載っていたのですが、ダメでした。
    Macのバージョンアップデートや再起動も併用しましたが上手くいきませんでした。

    $ sudo rm -rf /Library/Developer/CommandLineTools
    $ xcode-select --install 
  • 【VisionOS】炎のパーティクル再現

    【VisionOS】炎のパーティクル再現

    Rearity Composer Pro を使って炎のパーティクルを再現したので下記にその際の設定の画像を貼っておきます。

    Emmiter

    まずはエミッターの設定
    公式のDeveloperのビデオの中でも最初にエミッターからいじることをおすすめされていました。
    ポイントはサイズとEmitterShapeの選択欄で表現したい形に近いものを選ぶことです。

    Loopのチェックが入ってることを確認して、再生ボタンを押して、
    右サイドバーにある再生ボタン▶️を押して動作確認をしましょう。

    Particle

    次にパーティクルの設定です。
    炎のエフェクトはデフォルトでプリセットに入っていなかったので自分で設定しました。

    こんな感じです。

    Birth Rate の設定の数値を1000程度に設定すると、よりリアルな豊かな表現になるのですが、
    あまり数値を上げすぎると重くなるようなので低くすることを心がけました。

    低い値に設定すると、
    PropertiesのSizeを大きめに設定しないとスカスカになるので、
    少しチープな感じにもなりますが、逆にポップになっていいかなって感じで実装しました。

    炎を再現するときにカラーは中心部を明るくして、
    先端の方に少しブラックの入った垢に設定すると、煤けた感じが表現できていい感じでした。

    Noiseを設定すると、ランダム性が出るのでおすすめです。

    Attract の設定で上下方向(Y軸)の値を正の値にして、他の軸の方向を0にしないと風に靡かれたような表現になるので注意です。

    Vortexの設定をすると回転の表現になるので、一応設定しました。

    終わりに

    今後もVisionOSの開発もしていきます。仕事等の連絡あればフォームからお願いします。

  • 【SwiftUI】Videoがどうしても表示されない時

    【SwiftUI】Videoがどうしても表示されない時

    cannnot preview in this file

    というエラーが出て全く動画が読み込まれない時があります。

    そんな時は動画のファイルをXCodeで開いて、
    右のインスペクターからTarget MemberShipに自分のプロジェクトを選択しましょう。

    動画を扱うたびにこのことを忘れてパニクるので備忘録がてらです。

  • 【SwiftUI】カメラのフロントとバックの切り替え

    【SwiftUI】カメラのフロントとバックの切り替え

    SwiftUIを使ったカメラの実装で、フロントカメラとバックカメラの切り替えについての記事です。

    他サイトにもいろんな方法は乗っていると思いますが。自分のメモように残しておこうと思います。

    トグルボタンの設定

    カメラのアイコンのついたボタンです。これを押すとカメラが切り替わるようにしたいと思います。

    Button(action: {
        camera.switchCamera()
    }) {
        Image(systemName: "arrow.triangle.2.circlepath.camera")
            .font(.largeTitle)
            .padding()
    }
    .background(Color.white.opacity(0.7))
    .clipShape(Circle())
    

    機能

    前回の記事に以下コードを追記すればOKです。

    func switchCamera() {
            currentCameraPosition = currentCameraPosition == .front ? .back : .front
            updateCameraPosition()
        }

    全コード

    特に出し惜しみする必要もないので、ここまでのコードを下に全て載せます。

    コピペしてこれが動かなかったら、この記事がもう古くなったことでしょう。

    import SwiftUI
    import AVFoundation
    
    struct CameraView: View {
        @StateObject var camera = CameraViewModel()
    
        var body: some View {
            ZStack {
                CameraControll(camera: camera)
                    .edgesIgnoringSafeArea(.all)
                VStack {
                    Spacer()
                    Button(action: {
                        camera.switchCamera()
                    }) {
                        Image(systemName: "arrow.triangle.2.circlepath.camera")
                            .font(.largeTitle)
                            .padding()
                    }
                    .background(Color.white.opacity(0.7))
                    .clipShape(Circle())
                }
            }
        }
    }
    
    struct CameraControll: UIViewRepresentable {
        @ObservedObject var camera: CameraViewModel
    
        func makeUIView(context: Context) -> UIView { camera }
        func updateUIView(_ uiView: UIViewType, context: Context) {}
    }
    
    class CameraViewModel: UIView, ObservableObject {
        var previewLayer: AVCaptureVideoPreviewLayer?
        var currentCameraPosition: AVCaptureDevice.Position = .front
        var session = AVCaptureSession()
    
        override func layoutSubviews() {
            super.layoutSubviews()
            _ = initCaptureSession
            previewLayer?.frame = bounds
            fixCameraOrientation()
        }
    
        lazy var initCaptureSession: Void = {
            updateCameraPosition()
        }()
    
        private func updateCameraPosition() {
            session.inputs.forEach { session.removeInput($0) }
    
            guard let device = AVCaptureDevice.DiscoverySession(deviceTypes: [.builtInWideAngleCamera],
                                                                mediaType: .video,
                                                                position: .unspecified)
                    .devices.first(where: { $0.position == currentCameraPosition }),
                  let input = try? AVCaptureDeviceInput(device: device) else { return }
    
            session.addInput(input)
    
            if previewLayer == nil {
                let previewLayer = AVCaptureVideoPreviewLayer(session: session)
                layer.insertSublayer(previewLayer, at: 0)
                self.previewLayer = previewLayer
            }
    
            session.startRunning()
        }
    
        func switchCamera() {
            currentCameraPosition = currentCameraPosition == .front ? .back : .front
            updateCameraPosition()
        }
    
        private func fixCameraOrientation() {
            let rotation: CGFloat = CGFloat(getDeviceOrientation().rawValue) * 90
            previewLayer?.transform = CATransform3DMakeRotation(rotation / 180 * CGFloat.pi, 0, 0, 1)
        }
    
        private func getDeviceOrientation() -> Orientation {
            guard let interfaceOrientation = UIApplication.shared.windows.first(where: { $0.isKeyWindow })?.windowScene?.interfaceOrientation else {
                return .right
            }
            switch interfaceOrientation {
            case .landscapeLeft:
                return .left
            case .landscapeRight:
                return .right
            case .portrait:
                return .down
            case .portraitUpsideDown:
                return .up
            default:
                return .right
            }
        }
    }
    
    enum Orientation: Int {
        case right = -1, down = 0, left = 1, up = 2
    }
    
    #Preview {
        CameraView()
    }
    
  • 【SwiftUI】カメラ機能を使いたい 開発日誌

    【SwiftUI】カメラ機能を使いたい 開発日誌

    SwiftUIでカメラ機能を実装する方法についてです。

    基本機能の実装

    import SwiftUI
    import AVFoundation
    
    struct CameraContentView: View {
        var body: some View {
            CameraView()
                .edgesIgnoringSafeArea(.all)
        }
    }
    
    struct CameraView: UIViewRepresentable {
        func makeUIView(context: Context) -> UIView { BaseCameraView() }
        func updateUIView(_ uiView: UIViewType, context: Context) {}
    }
    
    class BaseCameraView: UIView {
        override func layoutSubviews() {
            super.layoutSubviews()
            _ = initCaptureSession
            (layer.sublayers?.first as? AVCaptureVideoPreviewLayer)?.frame = bounds
        }
    
        lazy var initCaptureSession: Void = {
            guard let device = AVCaptureDevice.DiscoverySession(deviceTypes: [.builtInWideAngleCamera],
                                                                mediaType: .video,
                                                                position: .unspecified)
                    .devices.first(where: { $0.position == .front }),
                  let input = try? AVCaptureDeviceInput(device: device) else { return }
    
            let session = AVCaptureSession()
            session.addInput(input)
            session.startRunning()
    
            layer.insertSublayer(AVCaptureVideoPreviewLayer(session: session), at: 0)
        }()
    }
    

    このコードを実行すると、フロントカメラが起動し、画面全体に映像が表示されます。

    画面の向きに合わせてカメラの向きを変える

    このままだと、端末の向きを変えた時に縦:横=16:9の比率を保ったまま回転してしまうので、画面の向きに応じたカメラの映像が出てこなくなってしました。

    そこで下記コードを追記しました。

    enum Orientation: Int {
        case right = -1, down = 0, left = 1, up = 2
    }
    
    private func getDeviceOrientation() -> Orientation {
        guard let interfaceOrientation = UIApplication.shared.windows.first(where: { $0.isKeyWindow })?.windowScene?.interfaceOrientation else {
            return .right
        }
        switch interfaceOrientation {
        case .landscapeLeft:
            return .left
        case .landscapeRight:
            return .right
        case .portrait:
            return .down
        case .portraitUpsideDown:
            return .up
        default:
            return .right
        }
    }
    
    private func fixCameraOrientation() {
        let rotation: CGFloat = CGFloat(getDeviceOrientation().rawValue) * 90
        previewLayer?.transform = CATransform3DMakeRotation(rotation / 180 * CGFloat.pi, 0, 0, 1)
    }
    

    これらの関数をlayoutSubviewsに追加することで、デバイスの向きが変わるたびにカメラの向きも適切に調整されます。

    参考文献様

    以下の記事に助けられました感謝

    https://zenn.dev/platina/articles/5b9683f6d50938

  • 【SwiftUI】動画の再生機能で横に黒い帯ができるのが嫌 MacOS

    【SwiftUI】動画の再生機能で横に黒い帯ができるのが嫌 MacOS

    MacOS向けのAppを制作しています。

    動画を再生する機能をつけたところ横に帯が出てしまいました。
    (下記画像:紫のボーダーの内側左右に黒い帯が出てしまっている)


    調べたところ、iOS向けにこれを消す記事があったのですがMacOSではこれが使えなかったので、試してみたことを記事に残しておきます。

    この記事のことをやるとした画像のようになります。角丸を飛び越えてしまってるので調子が必要そう…。

    事前準備 動画の再生まで

    まず、動画の準備から始めます。再生したい動画ファイルをプロジェクトに追加し、そのURLをAVPlayerにセットします。

    import SwiftUI
    import AVKit
    
    struct VideoView: View {
        private let player = AVPlayer(url: Bundle.main.url(forResource: "HowToSetIcon", withExtension: "mov")!)
    
        var body: some View {
            PlayerView(player: player)
        }
    }
    

    黒い帯を消す方法

    次に、AppKitの力を借りて、NSViewRepresentableを使用してAVPlayerViewをラップします。これにより、動画がビュー全体にフィットするように調整できます。

    struct PlayerView: NSViewRepresentable {
        let player: AVPlayer
    
        func makeNSView(context: Context) -> NSView {
            let playerView = AVPlayerView()
            playerView.player = player
            return playerView
        }
    
        func updateNSView(_ nsView: NSView, context: Context) {
            // none
        }
    }
    
    class AVPlayerView: NSView {
        var player: AVPlayer? {
            didSet {
                (self.layer as? AVPlayerLayer)?.player = player
            }
        }
    
        override init(frame frameRect: NSRect) {
            super.init(frame: frameRect)
            self.wantsLayer = true
        }
    
        required init?(coder: NSCoder) {
            super.init(coder: coder)
            self.wantsLayer = true
        }
    
        override var layer: CALayer? {
            didSet {
                guard let layer = self.layer as? AVPlayerLayer else { return }
                layer.videoGravity = .resizeAspectFill
            }
        }
    
        override func makeBackingLayer() -> CALayer {
            return AVPlayerLayer()
        }
    }
    

    これで、動画がフルスクリーンで表示され、再生や停止のUI部品は表示されません。これにより、動画の横に黒い帯が表示される問題を防ぐことができます。

    やっぱめんどくさい(追記)

    SwiftUI標準のプレイヤーだとホバー時に再生停止ボタンと、シークバーが標準で付いているので、AppKitを使ってしまうとそれを自分で構築しなければならないので大変…。

    最初にも書いたけど、CornerRadiusかけたときに切り取れるようにする方法がわからない。

    結局動画編集して比率を変更してAspectRatioを指定することにした。コードで切り取っていい感じにするのは諦めた…。

    VideoPlayer(player: player)
            .aspectRatio(4 / 3, contentMode: .fit)
  • 【SwiftUI】SwiftUIで表示と同時に動画を再生し、ループさせる方法

    【SwiftUI】SwiftUIで表示と同時に動画を再生し、ループさせる方法

    まず、必要なライブラリをインポートします。

    import SwiftUI
    import AVKit
    

    ここでSwiftUIはAppleが提供するUIツールキットで、AVKitはオーディオとビデオの再生をサポートするフレームワークです。

    次に、SampleViewという名前のViewを作成します。

    struct SampleView: View {
    

    このViewは、動画を再生するためのプレーヤーを持っています。

    private let player = AVPlayer(url: Bundle.main.url(forResource: "SampleVideo", withExtension: "mov")!)
    

    ここでAVPlayerは動画を再生するためのオブジェクトで、Bundle.main.url(forResource:withExtension:)メソッドを使ってアプリのメインバンドルから動画ファイルのURLを取得しています。

    次に、bodyプロパティを定義します。

    var body: some View {
    

    このプロパティは、Viewの内容を定義します。ここでは、VStackを使ってビデオプレーヤーを配置します。

    VStack {
        VideoPlayer(player: player)
    

    VideoPlayerAVKitが提供するビデオプレーヤーのViewで、先ほど作成したplayerを引数に取ります。

    そして、onAppear()メソッドを使って、Viewが表示されたときに動画を再生します。

    .onAppear() {
        player.play()
    

    さらに、NotificationCenterを使って動画が終了したときに動画を最初から再生するように設定します。

    NotificationCenter.default.addObserver(forName: .AVPlayerItemDidPlayToEndTime, object: player.currentItem, queue: .main) { _ in
        player.seek(to: .zero)
        player.play()
    }
    

    これにより、動画は無限にループします。

    以上が、SwiftUIを使って動画を再生し、ループさせる方法の解説です。

  • 【SwiftUI】SwiftUIで要素をN個づつ改行したい

    【SwiftUI】SwiftUIで要素をN個づつ改行したい

    よっしゃ、みんな!今日はSwiftUIでSwiftUIで要素をN個づつ改行する方法を教えてやるぜ!
    最終的には以下の画像みたいになったぜ!

    まず、こんな感じのコードがあるとするよな

    let colors: [Color] = [.red, .blue, .green, .yellow, .orange, .pink, .purple, .gray, .black, .white]
    
    HStack(spacing: 8) {
        ForEach(colors, id: \.self) { color in
            Circle()
                .fill(color)
                .frame(width: 8, height: 8)
                .tag(color)
                .onTapGesture {
                    self.selectedColor = color
                }
        }
    }
    .pickerStyle(.segmented)
    

    このコードで、色を一列に並べてるだけだけど、これを複数行に分けたいとき、どうすればいいかって話だ。

    それには、以下のようにすればいいんだよ

    let colors: [Color] = [.red, .blue, .green, .yellow, .orange, .pink, .purple, .gray, .black, .white]
    
    let itemsPerRow = 4
    
    VStack {
        ForEach(0..<(colors.count + itemsPerRow - 1) / itemsPerRow, id: \.self) { row in
            HStack(spacing: 8) {
                ForEach(0..<itemsPerRow, id: \.self) { item in
                    let index = row * itemsPerRow + item
                    if index < colors.count {
                        let color = colors[index]
                        Circle()
                            .fill(color)
                            .frame(width: 8, height: 8)
                            .tag(color)
                            .onTapGesture {
                                self.selectedColor = color
                            }
                    }
                }
            }
        }
    }
    .pickerStyle(.segmented)
    

    このコードで、行ごとの項目数(itemsPerRow)を設定して、それぞれの行と項目に対してForEachループを使って、
    各項目のインデックスをrow * itemsPerRow + itemで計算して、そのインデックスが色の配列の範囲内にある場合にだけ、対応する色の円を表示してるんだ。

    これで、色の配列を任意の数の項目ごとに分けて表示できるぜ!

    ただし、最後の行は他の行よりも項目数が少ない場合があるから、それが問題なら適切に調整してみてくれよな。

    また、spacingframeの値も必要に応じて調整してくれ。このコードはあくまで一例だからな!

    以上、SwiftUIでN個の要素を並べる方法の紹介だったぜ!お前らもぜひ試してみてくれよな!

  • 【SwiftUI】Formの中のSliderを幅いっぱいにしたいのにできない。MacOS

    【SwiftUI】Formの中のSliderを幅いっぱいにしたいのにできない。MacOS

    MacOS向けのAppを制作しています。

    SliderをFormの中で使うとラベル部分と、スライダー部分で横に2分割されてしまいます。
    下の写真が問題の現象。

    結局解決策がわからず、Formを使うことを諦め、以下のようになんちゃってFormをVstackで作ることにしました。
    結果的に以下のようにSliderを幅いっぱいに表示することができるようになりました。

    いかになんちゃってFormのセクションの役割をするVStackを貼っておきます。

    struct CustomVStack<Content: View>: View {
        let content: Content
    
        init(@ViewBuilder content: () -> Content) {
            self.content = content()
        }
    
        var body: some View {
            VStack(alignment: .leading, content: {
                content
            })
            .padding(.vertical, 12)
            .padding(.horizontal, 8)
            .frame(maxWidth: .infinity)
            .overlay(
                RoundedRectangle(cornerRadius: 4)
                    .stroke(Color.gray, lineWidth: 0.5)
            )
        }
    }
    

    使用するときは以下のように別Viewから呼び出してあげればOKです。

    CustomVStack {
        TextField("Text", text: $inputText)
            .font(.body)
        SizeSlider(textSize: $textSize)
        ColorPicker("Color", selection: $textColor)
    }
    
    CustomVStack {
        FolderColorPicker(folderColor: $folderColor)
    }
    
  • 【SwiftUI】SwiftUIでSliderを使って文字のサイズを変更する方法

    【SwiftUI】SwiftUIでSliderを使って文字のサイズを変更する方法

    みんな、こんにちは!今日はSwiftUIでSliderを使って文字のサイズを変更する方法を紹介するよ。これは特に、UIに数値を表示するときに役立つよ。

    どうやるの?

    まず、Sliderビューを作成し、そのvalueパラメータに文字のサイズを表す変数(この場合はtextSize)をバインドするんだ。そして、Sliderinパラメータで文字のサイズの範囲を指定するよ。

    Slider(value: $textSize, in: 32...200, step: 1)
    

    このコードでは、textSizeの値が32から200の範囲で変更できるようになっているんだ。

    次に、Textビューのfont修飾子を使って文字のサイズを変更するんだ。具体的には、font修飾子の引数に「.system(size:)」を指定し、その引数「size」に文字のサイズを表す変数(この場合はtextSize)を指定するよ。

    Text("Hello, SwiftUI")
        .font(.system(size: textSize))
    

    このコードでは、textSizeの値に応じて文字のサイズが変更されるよ。

    小数点以下を表示しないようにする

    Textビューのspecifierパラメータを使って書式を指定するんだ。具体的には、%0.fを指定することで小数点以下を表示しないようにすることができるんだ。

    Text("\(textSize, specifier: "%0.f")")
        .foregroundColor(.blue)
    

    このコードでは、textSizeの値を文字列に変換し、その際に小数点以下を表示しないようにしているんだ。

    まとめ

    以上の方法で、SwiftUIを使用してSliderを使って文字のサイズを変更することができるよ。

  • 【SwiftUI】Canvasの画像を動的に変更する

    【SwiftUI】Canvasの画像を動的に変更する

    画像の比率を保ったまま追加した画像の拡大縮小をしたかった。ただそれだけだった。

    前提条件

    この記事では、以下のようなSwiftUIのビューがあると仮定します。

    struct CanvasView: View {
        ...
        @State private var image: NSImage? = nil
        ...
    }
    
    struct CanvasToSave: View {
        ...
        @Binding var userImage: NSImage?
        ...
    }
    

    CanvasViewはユーザーが画像を選択できるビューで、選択した画像はimageに保存されます。CanvasToSaveはキャンバスに画像を描画するビューで、描画する画像はuserImageから取得します。

    スケールの追加

    まず、CanvasViewCanvasToSaveの両方に新たなBindingを追加します。これにより、ユーザーが画像のスケールを動的に変更できるようになります。

    // CanvasView.swift
    struct CanvasView: View {
        ...
        @State private var imageScale: CGFloat = 1.0
        ...
    }
    
    // CanvasToSave.swift
    struct CanvasToSave: View {
        ...
        @Binding var imageScale: CGFloat
        ...
    }
    

    次に、CanvasViewにスライダーを追加して、ユーザーが画像のスケールを調整できるようにします。

    // CanvasView.swift
    struct CanvasView: View {
        ...
        var body: some View {
            ...
            Form {
                ...
                Section("Image Scale") {
                    Slider(value: $imageScale, in: 0.1...2.0, step: 0.1)
                }
                ...
            }
        }
    }
    

    最後に、CanvasToSaveで画像を描画する際にスケールを適用します。

    // CanvasToSave.swift
    struct CanvasToSave: View {
        ...
        var body: some View {
            Canvas { context, size in
                ...
                if let img = userImage {
                    let userSelectImage = Image(nsImage: img)
                    let scaledSize = CGSize(width: img.size.width * imageScale, height: img.size.height * imageScale)
                    context.draw(userSelectImage, in: CGRect(x: midPoint.x - scaledSize.width / 2, y: midPoint.y - scaledSize.height / 2, width: scaledSize.width, height: scaledSize.height))
                    context.blendMode = GraphicsContext.BlendMode.softLight
                }
            }
        }
    }
    

    以上の変更により、ユーザーがスライダーを使用して画像のスケールを動的に変更できるようになります。画像のスケールは、CanvasToSaveuserSelectImageに反映されます。

  • 【SwiftUI】俺はただViewをPNGで書き出しただけなんだ

    【SwiftUI】俺はただViewをPNGで書き出しただけなんだ

    MacOSでViewをPNGで書き出しただけ。

    結構大変だったのでここに足跡を残しておこうと思う。試行錯誤もあったけれど、今はただ疲れたので完成したコードだけ載せておく。いろんなサイト様を参考にしたので、少し寝たら参考リンクを貼ろうと思う。

    以下コード

    //
    //  CanvasView.swift
    //  FolderCustomizer
    //
    //  Created by coiai on 2023/12/15.
    //
    
    import SwiftUI
    
    struct CanvasView: View {
        @State private var inputText = ""
        @State private var presented = true
        @State private var folderColor: FolderColorPicker.FolderColor = .Blue
        
        var body: some View {
            VStack {
                CanvasToSave(inputText: $inputText, folderColor: $folderColor)
            }
            .inspector(isPresented: $presented) {
                Button("Save Image") {
                   
                }
    
                Form {
                    Section("Text") {
                        TextField("Text", text: $inputText)
                            .font(.body)
                            .padding()
                    }
                    Section("Folder Color") {
                        FolderColorPicker(folderColor: $folderColor)
                    }
                }
                .inspectorColumnWidth(min: 200, ideal: 300, max: 400)
                .toolbar {
                    Spacer()
                    Button {
                        presented.toggle()
                    } label: {
                        Label("toggle inspector", systemImage: "info.circle")
                    }
                }
                Spacer()
            }
        }
    }
    
    struct CanvasToSave: View {
        @Binding var inputText: String
        @Binding var folderColor: FolderColorPicker.FolderColor
        
        var body: some View {
            Canvas { context, size in
                let rect = CGRect(origin: .zero, size: size).insetBy(dx: 5, dy: 5)
                // キャンバスの中心の点
                let midPoint = CGPoint(x: size.width / 2, y: size.height / 2)
                
                
                let text = Text(inputText)
                    .font(.largeTitle)
                    .fontWeight(.bold)
                    .foregroundColor(.white)
                
                context.draw(text, at: midPoint, anchor: .center)
                context.blendMode = GraphicsContext.BlendMode.softLight
                
                let image = Image(folderColor.rawValue)
                context.draw(image, in: rect.insetBy(dx: 0, dy: 0))
            } symbols: {
                RotateObjView()
                    .tag(0)
            }
            .frame(width: 512, height: 512)
        }
    }
    
    #Preview {
        CanvasView()
    }
    
  • React Three.js で Obj や gltf の表示で詰まったこと

    React Three.js で Obj や gltf の表示で詰まったこと

    どうしたの?

    自作のモデルをThree.jsで表示させようとした際に、モデルが表示されず困ってしまいました。

    対処法としてはオブジェクトを public の下に配置することで解決しました。

    完成したもの

    実際の作業

    ディレクトリ構成

    public
      |- object
      |  |- Monkey.mtl
      |  |_ Monkey.obj
      |_ etc...
    src
    package.json

    コード

    以下object3d.tsxのコードです。

    import React, { useRef, useEffect } from 'react';
    import * as THREE from 'three';
    import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls';
    import { OBJLoader } from 'three/examples/jsm/loaders/OBJLoader';
    import { MTLLoader } from 'three/examples/jsm/loaders/MTLLoader';
    
    const Object3D = () => {
      const mount = useRef<HTMLDivElement>(null);
    
      useEffect(() => {
        if (!mount.current) return;
    
        const width = mount.current.clientWidth;
        const height = mount.current.clientHeight;
    
        // scene, camera, and renderer initialization
        const scene = new THREE.Scene();
        const camera = new THREE.PerspectiveCamera(75, width / height, 0.1, 1000);
        const renderer = new THREE.WebGLRenderer({ antialias: true });
    
        // controls for mouse rotation
        const controls = new OrbitControls(camera, renderer.domElement);
        controls.enableDamping = true;
    
        // camera position
        camera.position.z = 2;
    
        // renderer settings
       const light = new THREE.PointLight(0xffffff, 1, 1000);
       light.position.set(0, 0, 10); // ライトの位置を設定します(x, y, z)
    
        // ライトをシーンに追加
        scene.add(light);
    
        // mount the renderer to the DOM
        mount.current.appendChild(renderer.domElement);
    
        // MTL and OBJ loader
        const mtlLoader = new MTLLoader();
        const objLoader = new OBJLoader();
        const MonkeyMtl = '/Objects/Monkey.mtl';
        const MonkeyObj = '/Objects/Monkey.obj';
    
        mtlLoader.load(
          MonkeyMtl,
          (materials) => {
            materials.preload();
            objLoader.setMaterials(materials);
            objLoader.load(
              MonkeyObj,
              (object) => {
                object.position.set(0, 0, 0); // set position to center
                object.scale.set(0.5, 0.5, 0.5); // scale down
                scene.add(object);
                console.log('Added object to scene');
              },
              (xhr) => {
                console.log((xhr.loaded / xhr.total * 100) + '% loaded');
              },
              (error) => {
                console.log('An error happened', error);
              }
            );
          },
          (xhr) => {
            console.log((xhr.loaded / xhr.total * 100) + '% loaded');
          },
          (error) => {
            console.log('An error happened', error);
          }
        );
    
        // animation
        const animate = () => {
          requestAnimationFrame(animate);
    
          // controls update
          controls.update();
    
          // rendering
          renderer.render(scene, camera);
        };
    
        // start animation
        animate();
    
        // handle resize
        const handleResize = () => {
          if (!mount.current) return;
    
          const width = mount.current.clientWidth;
          const height = mount.current.clientHeight;
    
          renderer.setSize(width, height);
          camera.aspect = width / height;
          camera.updateProjectionMatrix();
        };
    
        window.addEventListener('resize', handleResize);
    
        return () => {
          window.removeEventListener('resize', handleResize);
        };
      }, []);
    
      return (
        <div>
          <h2>モデルテスト</h2>
        <div
          ref={mount}
          style={{ width: '100%', height: '100vh' }}
        />
        </div>
      );
    };
    
    export default Object3D;
    

Home
About
Blog
Works
Contact