カテゴリー: 未分類

  • 【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個づつ改行する方法を教えてやるぜ!
    最終的には以下の画像みたいになったぜ!

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

    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

    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で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のビューがあると仮定します。

    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で書き出しただけなんだ

    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 の表示で詰まったこと

    どうしたの?

    自作のモデルを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;
    

  • Bing Chat AI の日本語フォントが嫌すぎる

    Bing Chat AI のフォントを直すプラグイン作った。

    BIng Chat AI は検索機能も付いているので、ChatGPTと使い分けている方もいるんじゃないかな?

    ただ日本語のフォントがひどすぎる、「多分アジアは全部同じフォントだろ。」みたいな感じで中華系のフォントを当てちゃったんだと思う。

    そんなわけでPC標準のフォントに差し替えるだけのChromeプラグイン作りました。

    無料なので使ってね! 良かったらストアで星ちょうだいね!(何の意味があるかわからないけど)

    https://chrome.google.com/webstore/detail/bing-chat-ai-%E3%83%95%E3%82%A9%E3%83%B3%E3%83%88%E3%82%92%E5%A4%89%E3%81%88%E3%81%9F%E3%81%84/gkjcpokolmabndhaonaepkchioaldhgi?hl=ja&authuser=0

    開発雑記

    特定のURLにアクセスしたときに発火する仕組みだと、ユーザーに権限の確認があるらしいです。ストアにアップした時に審査に時間かかるかもよ。となってたけど6時間くらいで普通に公開されて良かった。

    あと、特定の企業のプロダクトの名前がはいっちゃってるので弾かれるんじゃないかと思ったけど大丈夫で良かった。

    追記 2024/01/31

    チャットAiを使っている際にスクロールすると検索画面に飛ばされる動作が嫌すぎるので、
    バージョンアップで遷移されないような機能を追加しました。

  • 【React TS】WordPress Rest API で投稿一覧でサムネイルを出力する

    この記事は前回の続編です。

    WordPressのサムネイルはフィーチャーメディア (featured media) と呼ばれています。これを呼び出すには
    https://your-site.net/wp-json/wp/v2/posts
    から一旦 id を取り出した後、
    src=”https://your-site.net/wp-json/wp/v2/media/取り出したid
    を叩いきます。このjsonの中のguid.renderedを取り出せれば画像のURLをとってくることができます

    以下コードです。

    import React, { useState, useEffect } from 'react';
    import axios from 'axios';
    import { Link } from 'react-router-dom';
    
    interface Post {
      id: number;
      title: { rendered: string };
      featured_media: number;
    }
    
    const PostList: React.FC = () => {
      const [posts, setPosts] = useState<Post[]>([]);
      const [mediaUrls, setMediaUrls] = useState<string[]>([]);
    
      useEffect(() => {
        axios.get('https://your-site.net/wp-json/wp/v2/posts')
          .then(response => {
            setPosts(response.data);
            const mediaRequests = response.data.map((post: Post) => 
              axios.get(`https://your-site/wp-json/wp/v2/media/${post.featured_media}`)
            );
            return Promise.all(mediaRequests);
          })
          .then(mediaResponses => {
            const mediaUrls = mediaResponses.map(response => response.data.guid.rendered);
            setMediaUrls(mediaUrls);
          })
          .catch(error => {
            console.error('Error fetching data:', error);
          });
      }, []);
    
      return (
        <div>
          <h1>WordPress Post List</h1>
          <ul>
            {posts.map((post, index) => (
              <li key={post.id}>
                {/* 詳細ページへのリンク */}
                <Link to={`/post/${post.id}`}>
                  <img src={mediaUrls[index]} alt="" style={{ maxWidth: '100px', maxHeight: '100px' }} />
                  {post.title.rendered}
                </Link>
              </li>
            ))}
          </ul>
        </div>
      );
    };
    
    export default PostList;
    
  • WordPress RestAPI を使ってフロントReactでヘッドレスCMSにする

    WordPressで自作テーマを作るのは労力に対してできるものが微妙だなと思い、ヘッドレスCMSにしようと思いました。

    スタイリングは一旦無視して機能だけ解説します。動作は次の動画のようになります。

    WordPress の投稿をGETしてみる

    Terminalで下記コマンドを実行してみましょう

    curl -X GET "<https://your-wordpress-site.com/wp-json/wp/v2/posts>"

    TerminalでJSONファイルは見づらいためPostmanを使うのがおすすめです。

    React アプリをCreate

    Terminal を開いて好きなディレクトリに移動して下記コマンドを実行します。

    $ npx create-react-app my-ts-headless-cms-app --template typescript

    投稿一覧ページの作成

    srcディレクトリ内にPostList.tsxを作成します。

    import React, { useState, useEffect } from 'react';
    import axios from 'axios';
    
    interface Post {
      id: number;
      title: { rendered: string };
    }
    
    const PostList: React.FC = () => {
      const [posts, setPosts] = useState<Post[]>([]);
    
      useEffect(() => {
        axios.get('https://your-wordpress-site.com/wp-json/wp/v2/posts')
          .then(response => {
            setPosts(response.data);
          })
          .catch(error => {
            console.error('Error fetching data:', error);
          });
      }, []);
    
      return (
        <div>
          <h1>WordPress Post List</h1>
          <ul>
            {posts.map(post => (
              <li key={post.id}>{post.title.rendered}</li>
            ))}
          </ul>
        </div>
      );
    };
    
    export default PostList;
    

    App.tsx.を開いて書き換えましょう。

    import React from 'react';
    import './App.css';
    import PostList from './PostList';
    
    function App() {
      return (
        <div className="App">
          <PostList />
        </div>
      );
    }
    
    export default App;
    

    一旦ここで動作確認してみましょう!Termialを開いて

    $ npm start

    うまく起動できましたか??

    できたら詳細ページを作っていきましょう!

    詳細ページの作成

    axis のインストールをします

    $ npm i axis

    srcディレクトリ内に、PostDetail.tsx を作成します。

    import React, { useState, useEffect } from 'react';
    import { useParams } from 'react-router-dom';
    import axios from 'axios';
    
    interface Post {
      id: number;
      title: { rendered: string };
      content: { rendered: string };
    }
    
    const PostDetail: React.FC = () => {
      const { postId } = useParams<{ postId: string }>();
      const [post, setPost] = useState<Post | null>(null);
    
      useEffect(() => {
        axios.get(`https://your-wordpress-site.com/wp-json/wp/v2/posts/${postId}`)
          .then(response => {
            setPost(response.data);
          })
          .catch(error => {
            console.error('Error fetching data:', error);
          });
      }, [postId]);
    
      if (!post) {
        return <div>Loading...</div>;
      }
    
      return (
        <div>
          <h1>{post.title.rendered}</h1>
          <div dangerouslySetInnerHTML={{ __html: post.content.rendered }} />
        </div>
      );
    };
    
    export default PostDetail;
    

    react-router-dom をインストールしましょう!

    $ nom install react-router-dom

    App.tsx を書き換えます

    import React from 'react';
    import { BrowserRouter, Route } from 'react-router-dom';
    
    import PostList from './PostList';
    import PostDetail from './PostDetail';
    import './App.css';
    
    function App() {
      return (
        <BrowserRouter>
          <div className="App">
            <div className="Contents">
                <Route index path="/" element={<PostList/>} />
                <Route index path="/post/:postId" element={<PostDetail/>} />
            </div>
          </div>
        </BrowserRouter>
      );
    }
    
    export default App;
    

    リストから詳細への遷移を作る

    PostList.tsx を下記のように書き換えます。

    import React, { useState, useEffect } from 'react';
    import axios from 'axios';
    import { Link } from 'react-router-dom';
    
    interface Post {
      id: number;
      title: { rendered: string };
    }
    
    const PostList: React.FC = () => {
      const [posts, setPosts] = useState<Post[]>([]);
    
      useEffect(() => {
        axios.get('https://your-wordpress-site.com/wp-json/wp/v2/posts')
          .then(response => {
            setPosts(response.data);
          })
          .catch(error => {
            console.error('Error fetching data:', error);
          });
      }, []);
    
      return (
        <div>
          <h1>WordPress Post List</h1>
          <ul>
            {posts.map(post => (
              <li key={post.id}>
                {/* 詳細ページへのリンク */}
                <Link to={`/post/${post.id}`}>{post.title.rendered}</Link>
              </li>
            ))}
          </ul>
        </div>
      );
    };
    
    export default PostList;