カテゴリー: 未分類

  • Simple and Mobile-Friendly! Creating a Timeline Design with Just HTML and CSS

    What is this?

    This is a simple code example for creating a timeline style using only HTML and CSS.

    It can be useful in cases where you need to implement pure HTML and CSS, such as with Karami Shop or WordPress with limited customization options.

    The timeline is also mobile-friendly. Feel free to use it if you’d like!


    Code Introduction


    By implementing the following code, you can achieve the look shown below. This is a minimal style, so feel free to tweak it as needed.

    <!DOCTYPE html>
    <html lang="ja">
    <head>
      <meta charset="UTF-8">
      <meta name="viewport" content="width=device-width, initial-scale=1.0">
      <title>timeline</title>
      <link rel="stylesheet" href="./style.css">
    </head>
    <body>
      <div class="container">
        <!-- item box LEFT ここを繰り返す -->
        <div class="timeline__container">
          <div class="timeline__textBox">
            <div class="timeline__textBox__text left">
              text
            </div>
          </div>
          <div class="timeline__line">
            <div class="timeline__dot"></div>
          </div>
          <div class="timeline__date">
            2024
          </div>
        </div>
        <!-- item box 終わり -->
    
        <!-- item box RIGHT ここを繰り返す -->
        <div class="timeline__container">
    
          <div class="timeline__date left">
            2024
          </div>
          
          <div class="timeline__line">
            <div class="timeline__dot"></div>
          </div>
    
          <div class="timeline__textBox">
            <div class="timeline__textBox__text right">
              text
            </div>
          </div>
        </div>
        <!-- item box 終わり -->
    
        <!-- item box  LEFT ここを繰り返す -->
        <div class="timeline__container">
          <div class="timeline__textBox">
            <div class="timeline__textBox__text left">
              text
            </div>
          </div>
          <div class="timeline__line">
            <div class="timeline__dot"></div>
          </div>
          <div class="timeline__date">
            2024
          </div>
        </div>
        <!-- item box 終わり -->
    
        <!-- item box LEFT ここを繰り返す -->
        <div class="timeline__container">
          <div class="timeline__textBox">
            <div class="timeline__textBox__text left">
              text
            </div>
          </div>
          <div class="timeline__line">
            <div class="timeline__dot"></div>
          </div>
          <div class="timeline__date">
            2024
          </div>
        </div>
        <!-- item box 終わり -->
      </div>
    </body>
    </html>
    .timeline__container {
      display: grid;
      grid-template-columns: 1fr 150px 1fr;
      grid-template-rows: 1fr;
    }
    
    .timeline__textBox {
    }
    
    .left {
      margin-left: auto;
    }
    
    .right {
      margin-right: auto;
    }
    
    .timeline__textBox__text {
      background-color: rgb(224, 231, 233);
      max-width: 370px;
      padding: 32px;
      border-radius: 16px;
      margin-top: 8px;
      margin-bottom: 8px;
    }
    
    .timeline__line {
      height: 100%;
      display: flex;
      justify-content: center;
      position: relative;
    }
    
    .timeline__line::after {
      content: '';
      background-color: rgb(72, 176, 255);
      width: 4px;
      height: 100%;
    }
    
    .timeline__dot {
      width: 16px;
      height: 16px;
      background-color: rgb(72, 176, 255);
      border-radius: 999px;
      position: absolute;
      top: 50%;
      left: 50%;
      transform: translate(-50%, -50%);
    }
    
    .timeline__date {
      display: flex;
      align-items: center;
      color: gray;
      font-style: italic;
    }
    
    /* スマホ版のスタイル */
    @media (max-width: 768px) {
      .timeline__container {
        grid-template-columns: 100px 70px 1fr;
      }
    
      .timeline__textBox {
        grid-column: 3 / 4;
        grid-row: 1 / 2;
      }
    
      .timeline__date {
        grid-column: 1 / 2;
        grid-row: 1 / 2;
      }
    
      .timeline__line {
        grid-column: 2 / 3;
        grid-row: 1 / 2;
      }
    
      .left, .right {
        margin-left: 0;
        margin-right: 0;
      }
    }

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

    ❓ これは何

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

    🛠️ やり方

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

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

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

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

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

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

  • 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アプリをロリポップサーバーにデプロイする手順とその過程で遭遇した問題と解決方法を共有します。

    動作したコード

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

    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 時にエラーが出てインストールできない

    環境

    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】炎のパーティクル再現

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

    Emmiter

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

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

    Particle

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

    こんな感じです。

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

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

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

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

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

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

    終わりに

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

  • 【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でカメラ機能を実装する方法についてです。

    基本機能の実装

    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

    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)