[iOS] Image + Circle + frame

struct ChatMessage: Identifiable {
    let id = UUID()
    let text: String
    let isMe: Bool
    let avatar: String  // 画像名(URLの場合もOK)
}

struct ChatRow: View {
    let message: ChatMessage

    var body: some View {
        HStack(alignment: .bottom, spacing: 10) {

            // 左側に相手のアバター
            if !message.isMe {
                avatarView
            }

            // メッセージバブル
            Text(message.text)
                .padding()
                .background(message.isMe ? Color.blue : Color.gray.opacity(0.2))
                .foregroundColor(message.isMe ? .white : .black)
                .cornerRadius(12)

            // 右側に自分のアバター
            if message.isMe {
                avatarView
            }
        }
        .padding(.horizontal)
    }

    // アバター
    private var avatarView: some View {
        Image(message.avatar)
            .resizable()
            .scaledToFill()
            .frame(width: 40, height: 40)
            .clipShape(Circle())
    }
}
struct ChatView: View {
    let messages = [
        ChatMessage(text: "こんにちは!", isMe: false, avatar: "avatar1"),
        ChatMessage(text: "こんにちは〜!", isMe: true, avatar: "myAvatar"),
        ChatMessage(text: "調子どう?", isMe: false, avatar: "avatar1")
    ]

    var body: some View {
        ScrollView {
            VStack(spacing: 12) {
                ForEach(messages) { msg in
                    ChatRow(message: msg)
                }
            }
        }
    }
}

[iOS] Video Upload

import SwiftUI
import PhotosUI

struct ContentView: View {
    @State private var selectedItem: PhotosPickerItem?
    @State private var selectedImage: UIImage?
    @State private var selectedVideoURL: URL?

    var body: some View {
        VStack(spacing: 20) {

            // 選択 UI
            PhotosPicker(selection: $selectedItem,
                         matching: .any(of: [.images, .videos])) {
                Text("画像・動画を選択")
                    .padding()
                    .background(Color.blue)
                    .foregroundColor(.white)
                    .cornerRadius(8)
            }

            // 選択した画像があれば表示
            if let image = selectedImage {
                Image(uiImage: image)
                    .resizable()
                    .scaledToFit()
                    .frame(height: 200)
            }

            // 選択した動画の URL を表示
            if let url = selectedVideoURL {
                Text("動画URL: \(url.lastPathComponent)")
                    .font(.caption)
            }

            // アップロードボタン
            Button("アップロード") {
                upload()
            }
            .padding()
            .background(Color.green)
            .foregroundColor(.white)
            .cornerRadius(8)
        }
        .onChange(of: selectedItem) { newValue in
            Task {
                await loadSelectedItem()
            }
        }
        .padding()
    }

    // 画像 / 動画を読み込む
    func loadSelectedItem() async {
        guard let item = selectedItem else { return }

        // 画像読み込み
        if let data = try? await item.loadTransferable(type: Data.self),
           let image = UIImage(data: data) {
            self.selectedImage = image
            self.selectedVideoURL = nil
            return
        }

        // 動画読み込み
        if let url = try? await item.loadTransferable(type: URL.self) {
            self.selectedVideoURL = url
            self.selectedImage = nil
            return
        }
    }

    // アップロード処理
    func upload() {
        if let image = selectedImage {
            print("📤 画像アップロード: \(image)")
        }

        if let url = selectedVideoURL {
            print("📤 動画アップロード: \(url)")
        }
    }
}

[iOS]アニメーションとトランジション

import SwiftUI

struct ContentView: View {
    @State private var isBig = false

    var body: some View {
        VStack(spacing: 20) {
            Text("アニメーションの基本")
                .font(.title)

            Circle()
                .fill(.blue)
                .frame(width: isBig ? 200 : 100,
                       height: isBig ? 200 : 100)
                .animation(.easeInOut(duration: 0.5), value: isBig)

            Button("サイズ変更") {
                isBig.toggle()
            }
        }
        .padding()
    }
}
import SwiftUI

struct ContentView: View {
    @State private var showBox = false

    var body: some View {
        VStack(spacing: 20) {
            Text("トランジションの基本")
                .font(.title)

            if showBox {
                Rectangle()
                    .fill(.green)
                    .frame(width: 200, height: 150)
                    .transition(.slide)         // ← これがトランジション
                    .animation(.easeInOut, value: showBox)
            }

            Button(showBox ? "消す" : "表示") {
                showBox.toggle()
            }
        }
        .padding()
    }
}
import SwiftUI

struct ContentView: View {
    @State private var showCard = false

    var body: some View {
        VStack(spacing: 20) {
            Text("アニメーション + トランジション")
                .font(.title)

            if showCard {
                RoundedRectangle(cornerRadius: 16)
                    .fill(.orange)
                    .frame(width: 250, height: 160)
                    .shadow(radius: 10)
                    .padding()
                    .transition(.opacity.combined(with: .scale))
                    .animation(.spring(response: 0.4, dampingFraction: 0.6), value: showCard)
            }

            Button(showCard ? "隠す" : "表示") {
                showCard.toggle()
            }
        }
        .padding()
    }
}

[iOS] Push通知

実際に「配信」するには サーバー・APNs・証明書 が必要なので複雑

✅ まずは “ローカル通知” の超簡単デモ(その場で動く通知)
プッシュ通知に進む前に、
iOS での 通知許可の取り方・通知の基本 が理解できます。
📌 サンプル:ボタンを押すと10秒後に通知が表示される
NotificationManager.swift

import Foundation
import UserNotifications

class NotificationManager {
    static let shared = NotificationManager()

    // 通知の許可をリクエスト
    func requestPermission() {
        UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .sound, .badge]) { success, error in
            if success {
                print("通知が許可されました")
            } else if let error = error {
                print("通知の許可エラー:", error)
            }
        }
    }

    // ローカル通知をスケジュール
    func scheduleNotification() {
        let content = UNMutableNotificationContent()
        content.title = "テスト通知"
        content.body = "10秒後に届く通知です"
        content.sound = .default

        let trigger = UNTimeIntervalNotificationTrigger(timeInterval: 10, repeats: false)

        let request = UNNotificationRequest(identifier: UUID().uuidString, content: content, trigger: trigger)

        UNUserNotificationCenter.current().add(request)
    }
}
import SwiftUI

struct ContentView: View {
    var body: some View {
        VStack(spacing: 20) {
            Text("通知テスト")
                .font(.title)

            Button("通知を許可") {
                NotificationManager.shared.requestPermission()
            }

            Button("10秒後に通知送信") {
                NotificationManager.shared.scheduleNotification()
            }
        }
        .padding()
    }
}

iOSのプッシュ通知の本当の仕組み
あなたのサーバー → APNs(Appleの通知サーバー) → iPhoneに通知

1. アプリが Apple に「通知を受けたい」と登録
→ Apple が端末専用の デバイストークン(Device Token) を発行
→ アプリはこのトークンを サーバー に送る
2. サーバーはそのトークンを使って Apple(APNs) に通知を送る
→ Apple が iPhone にプッシュ通知を配信する

push通知を送る最低限のコード

import SwiftUI
import UserNotifications

@main
struct PushDemoApp: App {
    @UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate

    var body: some Scene {
        WindowGroup {
            ContentView()
        }
    }
}

class AppDelegate: NSObject, UIApplicationDelegate, UNUserNotificationCenterDelegate {

    // 起動時に通知登録
    func application(
        _ application: UIApplication,
        didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil
    ) -> Bool {

        UNUserNotificationCenter.current().delegate = self

        // 通知許可のリクエスト
        UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .badge, .sound]) { granted, error in
            if granted {
                DispatchQueue.main.async {
                    UIApplication.shared.registerForRemoteNotifications()
                }
            }
        }

        return true
    }

    // APNs からデバイストークン取得
    func application(_ application: UIApplication,
                     didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {

        let tokenString = deviceToken.map { String(format: "%02.2hhx", $0) }.joined()
        print("Device Token:", tokenString)
    }

    // トークン取得失敗時
    func application(_ application: UIApplication,
                     didFailToRegisterForRemoteNotificationsWithError error: Error) {
        print("Failed to get token:", error.localizedDescription)
    }
}

🔹 このコードでできること
通知許可ダイアログが表示される
トークンが取得できたら Xcode のコンソールに表示される
→ サーバーに送る必要がある

📌 注意
実際のプッシュ通知を送るには、さらに:
🔐 Apple Developer の作業が必要
APNs 証明書 or Key
Push Notifications を有効化
プロビジョニングプロファイル更新

🖥️ サーバー実装も必要(例:Python / Node.js / Firebase など)
APNs に対して HTTPS/JSON で通知を送る

APNsとは

What is APNs?
Apple Push Notification Service (APNs) is the core of remote notification functionality. It is a robust, secure, and very efficient service for application developers to deliver information to iOS(and indirectly watchOS), tvOS, and macOS devices.
なるほど。

The four main methods used on the app’s introductory screen

Splash
It is a technique called splash that is adopted by most applications. This screen appears when you tap the app icon from home, and it is used to earn data loading time or to make the app known to the user as a brand.

However, since it is only for the purpose of earning time and recognizing the brand, it is not compatible with tool-based apps. The user who uses the tool wants to “use it quickly and repeatedly”.

Walk through
A technique called walk-through is a method that allows the user to gradually understand the features and usage of the app using slides instead of letting the app touch the app immediately after launching the app. Often see people who call it “Tutorial”, but the official name for this is Walkthrough.

Coach mark
Overlays and pop-ups appear on the normal screen of the app. Use arrows to explain the function of each button and what its contents mean. If the UI becomes complicated and it is determined that “a user who has just installed the app will be confused if you look at this screen”, you should adop it.

Empty state
It may be an unfamiliar word, but the point is “the guy who displays when the content is empty”. The purpose of this screen is “convey information that there is no content now” and “convey action to be performed next”.
It may be more user-friendly to have an empty state for apps that do not display content if you do not follow someone, or for apps such as Notepad that create content yourself.

Assets.xcassetsに画像を置く

グー、チョキ、パーの画像を置いていきます。

これをどうやって、controller.swfitで呼び出すんだ?
let image = UIImage(named: “Image2”)でコンパイルしてみる。

override func viewDidLoad() {
        super.viewDidLoad()
        let image = UIImage(named: "Image2")
        imageView.image = image
        imageView.contentMode = UIViewContentMode.center
        self.view.addSubview(imageView)
        // Do any additional setup after loading the view, typically from a nib.
    }

あれ、UIImage(named: “ファイル名”)でいけますね。
すると、/app/hoge.jpg と /app/Aseets.xcassets/hoge 共にUIImage(named: “hoge”)で読み込める、ということですな。 詳細はわかりませんが、コンパイルする際に、同階層として扱っているんでしょう。

では、ボタンを押したら、グーチョキパーがランダムで表示されるよう作っていきたいと思います。

Assets.xcassetsでAppIconを設定する

Asset Catalog ファイル(.xcassets):
画像を一元管理出来るようになり、画像ファイルの追加・削除・修正を行ってもプロジェクトファイル(.pbxproj)は変更されない

早速置いてみましょう。

Assets.xcassetsを押下すると、以下のような画面になります。

AppIconがデフォルトで入っています。AppIconをクリックすると

retina ディスプレイ対応を2x, 3xで表しています。
2xは、2倍ですね。

以下の画像をおきます。

あら、should be 80 x 80pix と言われました。

とりあえず、エミュレーターをbuildして見ます。
なに、なんか知らんがエラーになった。

やっぱり120px x 120でないとダメみたい。
本当なら、psdで編集したいが、横着してtoolでresizeします。

エラーが消えたのでコンパイルします。
bui1ld succeeded!
いいね、これ好きです。

うお、warningが出まくってる

きた! icon変わりました。niceです。

imageView.contentModeでエラーになった時

まず画像を用意する
チョキは適当な画像が見当たらなかった為、ピースサイン

続いてStory boardにUIImageViewを配置する
control でviweController.swiftに繋げて、UIImage(named: “janken_goo”)と書く
let image = UIImage(named: “janken_goo”)
imageView.image = image

class ViewController: UIViewController {

    @IBOutlet weak var imageView: UIImageView!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        let image = UIImage(named: "janken_goo")
        imageView.image = image
        // Do any additional setup after loading the view, typically from a nib.
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }


}

UIImageViewの画像サイズのままになっている。。
48x48pixなんだけどな。
それと、今日、本屋で立ち読みした本には、画像はAssets.xcassestsに置くと書いてあったが。。swift書くまえにいきなりつまづいた。

imageView.contentMode で画像の縦横サイズを指定出来るらしい。

override func viewDidLoad() {
        super.viewDidLoad()
        let image = UIImage(named: "janken_goo")
        imageView.image = image
        imageView.contentMode = UIViewContentMode.Center
        self.view.addSubview(imageView)
        // Do any additional setup after loading the view, typically from a nib.
    }

UIViewContentMode.Centerでerror, build出来ない。
なに???

いろいろなサイトを横断して、appleのdeveloper siteのuiviewcontentmodeを見てみる
https://developer.apple.com/documentation/uikit/uiviewcontentmode

case center
The option to center the content in the view’s bounds, keeping the proportions the same.

なに? centerは小文字?
imageView.contentMode = UIViewContentMode.centerで再度build

override func viewDidLoad() {
        super.viewDidLoad()
        let image = UIImage(named: "janken_goo")
        imageView.image = image
        imageView.contentMode = UIViewContentMode.center
        self.view.addSubview(imageView)
        // Do any additional setup after loading the view, typically from a nib.
    }

おおおお、ファイヤー
iOS開発で初めてちょっと仕事した!