
Swiftui Components
- 9 installs
- 3 repo stars
- Updated June 5, 2026
- xtone/ai_development_tools
Helps with ai & agent building tasks.
About
swiftui-components is a Claude Code skill for ai & agent building. It helps solo builders move faster with AI-assisted development.
- swiftui-components
- AI & Agent Building
- AI-coding skill
Swiftui Components by the numbers
- 9 all-time installs (skills.sh)
- +1 installs in the week ending Jul 27, 2026 (Skillselion tracking)
- Ranked #12,152 of 16,546 AI & Agent Building skills by installs in the Skillselion catalog
- Data as of Jul 27, 2026 (Skillselion catalog sync)
npx skills add https://github.com/xtone/ai_development_tools --skill swiftui-componentsAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 9 |
|---|---|
| repo stars | ★ 3 |
| Last updated | June 5, 2026 |
| Repository | xtone/ai_development_tools ↗ |
What it does
Helps with ai & agent building tasks.
Files
SwiftUI UIコンポーネントカタログ
iOS 16以降で利用可能なSwiftUIコンポーネントを機能別に整理したリファレンス。
ディレクトリ構成
swiftui-components/
├── SKILL.md (このファイル)
└── references/
├── navigation.md # ナビゲーション
├── charts.md # チャート・グラフ
├── scroll.md # スクロール
├── media.md # メディア・共有
├── animation.md # アニメーション
├── layout.md # レイアウト
├── tips.md # TipKit
├── preview.md # プレビュー
├── container.md # コンテナ値
├── theming.md # テーマ・デザイン
└── webview.md # WebViewリファレンスファイル
references/navigation.md
ナビゲーション関連コンポーネント:
- NavigationStack (iOS 16+): NavigationViewの後継、プログラマティックナビゲーション
- NavigationSplitView (iOS 16+): 2列/3列レイアウト対応
- NavigationPath (iOS 16+): 複数の型をサポートするナビゲーションパス
- TabView刷新 (iOS 26+): 新デザイン・アニメーション
references/charts.md
チャート・グラフ関連コンポーネント:
- Swift Charts (iOS 16+): LineMark, BarMark, AreaMark, PointMark等
- Chart3D (iOS 26+): 3Dチャートのネイティブサポート
references/scroll.md
スクロール関連コンポーネント:
- scrollPosition (iOS 17+): スクロール位置の取得・設定
- scrollTargetBehavior (iOS 17+): スナップスクロール
- containerRelativeFrame (iOS 17+): 親コンテナに対する相対サイズ指定
references/media.md
メディア・共有関連コンポーネント:
- PhotosPicker (iOS 16+): 写真ライブラリアクセス不要の写真選択
- ShareLink (iOS 16+): 標準シェア機能のSwiftUI対応
- Transferable (iOS 16+): データ転送プロトコル
references/animation.md
アニメーション関連コンポーネント:
- symbolEffect (iOS 17+): SF Symbolsアニメーション
- contentTransition (iOS 17+): テキスト・コンテンツ変更アニメーション
- phaseAnimator (iOS 17+): 複数フェーズのアニメーション
- keyframeAnimator (iOS 17+): キーフレームベースのアニメーション
- @Animatable (iOS 26+): アニメーション対応マクロ
references/layout.md
レイアウト関連コンポーネント:
- Grid (iOS 16+): 柔軟なグリッドレイアウト
- ViewThatFits (iOS 16+): スペースに応じた自動ビュー選択
- AnyLayout (iOS 16+): 動的レイアウト切り替え
- Gauge (iOS 16+): 進捗・レベル表示
- Table (iOS 16+): 表形式のデータ表示(macOS/iPadOS)
- ToolbarSpacer (iOS 26+): ツールバースペース制御
- labelIconToTitleSpacing (iOS 26+): Labelスペース調整
references/tips.md
TipKitフレームワーク:
- TipKit (iOS 17+): 機能発見ヒント表示
references/preview.md
プレビュー関連:
- #Preview (iOS 17+): プレビューマクロの簡略化
- @Previewable (iOS 18+ / Xcode 16+): プレビュー内で直接@State使用
references/container.md
コンテナ値・トランザクション:
- ContainerValues (iOS 18+): 子→親への値伝達
- Transaction (iOS 18+): アニメーション制御
references/theming.md
テーマ・デザイン:
- Liquid Glass (iOS 26+): iOS 7以来最大のUI刷新
- .liquidGlass() (iOS 26+): Liquid Glassエフェクト
- .depthLayer() (iOS 26+): 深度レイヤー設定
references/webview.md
WebView関連:
- WebView (iOS 26+): SwiftUIネイティブWebView
- Scene Bridging (iOS 26+): UIKit/AppKitとSwiftUIシーンの統合
バージョン対応表(概要)
| コンポーネント | iOS | 備考 |
|---|---|---|
| NavigationStack | 16+ | NavigationViewの後継 |
| Swift Charts | 16+ | 宣言的チャート |
| PhotosPicker | 16+ | 権限不要の写真選択 |
| Grid | 16+ | 柔軟なグリッド |
| TipKit | 17+ | 機能発見ヒント |
| scrollPosition | 17+ | スクロール位置制御 |
| symbolEffect | 17+ | SFシンボルアニメ |
| #Preview | 17+ | 簡易プレビュー |
| ContainerValues | 18+ | 子→親値伝達 |
| @Previewable | 18+ | プレビュー内@State |
| Liquid Glass | 26+ | 新デザイン言語 |
| WebView | 26+ | ネイティブWebView |
| @Animatable | 26+ | アニメーションマクロ |
| Chart3D | 26+ | 3Dチャート |
使用方法
新しいUIを実装する際
1. 実装したい機能に対応するリファレンスファイルを参照 2. バージョン対応表でプロジェクトのデプロイメントターゲットとの互換性を確認 3. コード例を参考に実装
利用可能なコンポーネントを探す際
1. バージョン対応表でデプロイメントターゲット以下のコンポーネントを確認 2. 該当するリファレンスファイルで詳細を確認
関連スキル
- swift-ios-migration: 移行ガイド(@Observable、Swift 6等)
- swiftui-coding-guidelines: コーディングガイドライン
- swiftui-ssot: 状態管理の設計
アニメーションコンポーネント
バージョン対応表
| コンポーネント | iOS | iPadOS | macOS | 備考 |
|---|---|---|---|---|
| symbolEffect | 17+ | 17+ | 14+ | SF Symbolsアニメーション |
| contentTransition | 17+ | 17+ | 14+ | コンテンツ変更アニメ |
| phaseAnimator | 17+ | 17+ | 14+ | 複数フェーズアニメ |
| keyframeAnimator | 17+ | 17+ | 14+ | キーフレームアニメ |
| sensoryFeedback | 17+ | 17+ | - | 触覚フィードバック |
| @Animatable | 26+ | 26+ | 26+ | アニメーション対応マクロ |
---
symbolEffect (iOS 17+)
SF Symbolsに対してアニメーション効果を適用。
バウンスエフェクト
@State private var notificationCount = 0
Image(systemName: "bell")
.symbolEffect(.bounce, value: notificationCount)
Button("通知") {
notificationCount += 1
}パルスエフェクト(繰り返し)
Image(systemName: "heart.fill")
.symbolEffect(.pulse.wholeSymbol, options: .repeating)可変カラー
Image(systemName: "wifi")
.symbolEffect(.variableColor.iterative, options: .repeating)制御付きアニメーション
@State private var isAnimating = false
Image(systemName: "arrow.down.circle")
.symbolEffect(.bounce, options: .repeat(3), value: isAnimating)
Button("アニメーション開始") {
isAnimating.toggle()
}置換アニメーション
@State private var isPlaying = false
Image(systemName: isPlaying ? "pause.fill" : "play.fill")
.contentTransition(.symbolEffect(.replace))
Button("切り替え") {
withAnimation {
isPlaying.toggle()
}
}---
contentTransition (iOS 17+)
テキストやコンテンツの変更をアニメーション化。
数値のアニメーション
struct CounterView: View {
@State private var count = 0
var body: some View {
Text("\(count)")
.font(.largeTitle)
.contentTransition(.numericText())
Button("増加") {
withAnimation {
count += 1
}
}
}
}補間アニメーション
Text(status)
.contentTransition(.interpolate) // 補間アニメーションシンボル置換
Image(systemName: isFavorite ? "star.fill" : "star")
.contentTransition(.symbolEffect(.replace))---
phaseAnimator (iOS 17+)
複数フェーズのアニメーションを定義。
基本的な使用法
struct PulsingView: View {
var body: some View {
Circle()
.fill(.blue)
.phaseAnimator([false, true]) { content, phase in
content
.scaleEffect(phase ? 1.2 : 1.0)
.opacity(phase ? 0.5 : 1.0)
} animation: { phase in
.easeInOut(duration: 0.5)
}
}
}カスタムフェーズ
enum AnimationPhase: CaseIterable {
case initial, scaled, rotated, final
}
Circle()
.phaseAnimator(AnimationPhase.allCases) { content, phase in
content
.scaleEffect(phase == .scaled ? 1.5 : 1.0)
.rotationEffect(.degrees(phase == .rotated ? 180 : 0))
} animation: { phase in
switch phase {
case .initial: .easeIn(duration: 0.3)
case .scaled: .spring(duration: 0.5)
case .rotated: .easeOut(duration: 0.4)
case .final: .linear(duration: 0.2)
}
}トリガー付き
@State private var trigger = false
Circle()
.phaseAnimator([0, 1, 2], trigger: trigger) { content, phase in
content
.scaleEffect(1.0 + Double(phase) * 0.1)
}
Button("Animate") {
trigger.toggle()
}---
keyframeAnimator (iOS 17+)
キーフレームベースの詳細なアニメーション制御。
基本的な使用法
struct BouncingView: View {
@State private var trigger = false
var body: some View {
Circle()
.keyframeAnimator(
initialValue: AnimationValues(),
trigger: trigger
) { content, value in
content
.scaleEffect(value.scale)
.offset(y: value.verticalOffset)
} keyframes: { _ in
KeyframeTrack(\.scale) {
SpringKeyframe(1.2, duration: 0.2)
SpringKeyframe(1.0, duration: 0.2)
}
KeyframeTrack(\.verticalOffset) {
LinearKeyframe(-50, duration: 0.15)
SpringKeyframe(0, duration: 0.3)
}
}
Button("Bounce") {
trigger.toggle()
}
}
}
struct AnimationValues {
var scale = 1.0
var verticalOffset = 0.0
}複雑なアニメーション
struct ComplexAnimationValues {
var scale = 1.0
var rotation = 0.0
var opacity = 1.0
var xOffset = 0.0
}
Rectangle()
.keyframeAnimator(
initialValue: ComplexAnimationValues(),
trigger: trigger
) { content, value in
content
.scaleEffect(value.scale)
.rotationEffect(.degrees(value.rotation))
.opacity(value.opacity)
.offset(x: value.xOffset)
} keyframes: { _ in
KeyframeTrack(\.scale) {
CubicKeyframe(1.5, duration: 0.3)
CubicKeyframe(1.0, duration: 0.3)
}
KeyframeTrack(\.rotation) {
LinearKeyframe(360, duration: 0.6)
}
KeyframeTrack(\.opacity) {
LinearKeyframe(0.5, duration: 0.3)
LinearKeyframe(1.0, duration: 0.3)
}
}---
sensoryFeedback (iOS 17+)
触覚フィードバックの簡易化。UIFeedbackGeneratorの代替。
基本的な使用法
struct InteractiveView: View {
@State private var isSelected = false
var body: some View {
Button("選択") {
isSelected.toggle()
}
.sensoryFeedback(.selection, trigger: isSelected)
}
}成功・エラーフィードバック
Button("送信") {
submit()
}
.sensoryFeedback(.success, trigger: submitSucceeded)
.sensoryFeedback(.error, trigger: submitFailed)カスタム条件
.sensoryFeedback(trigger: errorOccurred) { oldValue, newValue in
newValue ? .error : nil
}フィードバックタイプ
.sensoryFeedback(.impact) // 衝撃
.sensoryFeedback(.selection) // 選択
.sensoryFeedback(.success) // 成功
.sensoryFeedback(.warning) // 警告
.sensoryFeedback(.error) // エラー
.sensoryFeedback(.increase) // 増加
.sensoryFeedback(.decrease) // 減少---
@Animatable マクロ (iOS 26+)
従来の animatableData ボイラープレートを削減するマクロ。
Before (iOS 25以前)
struct LoadingArc: Shape {
var center: CGPoint
var radius: CGFloat
var startAngle: Angle
var endAngle: Angle
var drawPathClockwise: Bool
var animatableData: AnimatablePair<
AnimatablePair<CGPoint.AnimatableData, CGFloat>,
AnimatablePair<Angle.AnimatableData, Angle.AnimatableData>
> {
get {
AnimatablePair(
AnimatablePair(center.animatableData, radius),
AnimatablePair(startAngle.animatableData, endAngle.animatableData)
)
}
set {
center.animatableData = newValue.first.first
radius = newValue.first.second
startAngle.animatableData = newValue.second.first
endAngle.animatableData = newValue.second.second
}
}
func path(in rect: CGRect) -> Path {
// パス生成ロジック
}
}After (iOS 26+)
import SwiftUI
@Animatable
struct LoadingArc: Shape {
var center: CGPoint
var radius: CGFloat
var startAngle: Angle
var endAngle: Angle
@AnimatableIgnored
var drawPathClockwise: Bool
func path(in rect: CGRect) -> Path {
// パス生成ロジック
Path()
}
}@AnimatableIgnored
アニメーション対象から除外するプロパティに使用。
@Animatable
struct CustomShape: Shape {
var animatedValue: CGFloat // アニメーション対象
@AnimatableIgnored
var staticConfig: Configuration // アニメーション対象外
func path(in rect: CGRect) -> Path {
// ...
}
}---
関連ドキュメント
チャート・グラフコンポーネント
バージョン対応表
| コンポーネント | iOS | iPadOS | macOS | 備考 |
|---|---|---|---|---|
| Swift Charts | 16+ | 16+ | 13+ | 宣言的チャートフレームワーク |
| LineMark | 16+ | 16+ | 13+ | 折れ線グラフ |
| BarMark | 16+ | 16+ | 13+ | 棒グラフ |
| AreaMark | 16+ | 16+ | 13+ | 面グラフ |
| PointMark | 16+ | 16+ | 13+ | 散布図 |
| RectangleMark | 16+ | 16+ | 13+ | 矩形マーク |
| RuleMark | 16+ | 16+ | 13+ | 線(軸線・参照線) |
| Chart3D | 26+ | 26+ | - | 3Dチャート |
---
Swift Charts (iOS 16+)
iOS 16で導入された宣言的なチャートフレームワーク。
基本的な折れ線グラフ
import Charts
struct SalesData: Identifiable {
let id = UUID()
let month: String
let sales: Int
}
struct SalesChart: View {
let data: [SalesData]
var body: some View {
Chart(data) { item in
LineMark(
x: .value("月", item.month),
y: .value("売上", item.sales)
)
}
}
}棒グラフ
Chart(data) { item in
BarMark(
x: .value("カテゴリ", item.category),
y: .value("値", item.value)
)
.foregroundStyle(by: .value("タイプ", item.type))
}面グラフ
Chart(data) { item in
AreaMark(
x: .value("日付", item.date),
y: .value("値", item.value)
)
.foregroundStyle(.blue.opacity(0.3))
}散布図
Chart(data) { item in
PointMark(
x: .value("X", item.x),
y: .value("Y", item.y)
)
.symbolSize(item.size)
}カスタマイズ
Chart(data) { item in
LineMark(
x: .value("日付", item.date),
y: .value("値", item.value)
)
.interpolationMethod(.catmullRom) // 曲線補間
.symbol(Circle())
.foregroundStyle(.blue)
}
.chartXAxis {
AxisMarks(values: .stride(by: .month)) { value in
AxisValueLabel(format: .dateTime.month(.abbreviated))
}
}
.chartYAxis {
AxisMarks(position: .leading)
}
.chartLegend(position: .bottom)複合チャート
Chart {
ForEach(salesData) { item in
BarMark(
x: .value("月", item.month),
y: .value("売上", item.sales)
)
.foregroundStyle(.blue.opacity(0.5))
}
ForEach(targetData) { item in
LineMark(
x: .value("月", item.month),
y: .value("目標", item.target)
)
.foregroundStyle(.red)
.lineStyle(StrokeStyle(lineWidth: 2, dash: [5, 3]))
}
RuleMark(y: .value("平均", averageValue))
.foregroundStyle(.green)
.annotation(position: .trailing) {
Text("平均")
.font(.caption)
}
}インタラクティブなチャート
struct InteractiveChart: View {
@State private var selectedElement: SalesData?
var body: some View {
Chart(data) { item in
BarMark(
x: .value("月", item.month),
y: .value("売上", item.sales)
)
.opacity(selectedElement == nil || selectedElement?.id == item.id ? 1 : 0.3)
}
.chartOverlay { proxy in
GeometryReader { geometry in
Rectangle()
.fill(.clear)
.contentShape(Rectangle())
.gesture(
DragGesture(minimumDistance: 0)
.onChanged { value in
let location = value.location
if let month: String = proxy.value(atX: location.x) {
selectedElement = data.first { $0.month == month }
}
}
.onEnded { _ in
selectedElement = nil
}
)
}
}
}
}---
Chart3D (iOS 26+)
iOS 26で導入された3Dチャートのネイティブサポート。
基本的な使用法
import Charts
Chart3D {
// PointMark, RuleMark, RectangleMark, SurfacePlot
}3D散布図
Chart3D(data) { item in
PointMark(
x: .value("X", item.x),
y: .value("Y", item.y),
z: .value("Z", item.z)
)
}注意: Chart3Dは現在iPadOSとmacOSでは利用できません。
---
移行チェックリスト
Chart導入
1. [ ] import Chartsを追加 2. [ ] データモデルをIdentifiableに準拠 3. [ ] 適切なMark(LineMark, BarMark等)を選択 4. [ ] 軸とレジェンドをカスタマイズ
---
関連ドキュメント
コンテナ値・トランザクション
バージョン対応表
| コンポーネント | iOS | iPadOS | macOS | 備考 |
|---|---|---|---|---|
| ContainerValues | 18+ | 18+ | 15+ | 子→親への値伝達 |
| @Entry | 18+ | 18+ | 15+ | ContainerValue定義マクロ |
| Transaction | 18+ | 18+ | 15+ | アニメーション制御強化 |
---
ContainerValues (iOS 18+)
子Viewから親コンテナに構成情報を伝える新しい仕組み。Preference Keyより直感的で型安全。
基本的な使い方
// 1. ContainerValueを定義
extension ContainerValues {
@Entry var columnSpan: Int = 1
}
// 2. 子Viewで値を設定
struct WideCard: View {
var body: some View {
CardContent()
.containerValue(\.columnSpan, 2) // 2列分を占有
}
}
// 3. 親コンテナで値を読み取り
struct CustomGrid: View {
var body: some View {
Grid {
ForEach(subviewOf: self) { subview in
let span = subview.containerValues.columnSpan
GridRow {
subview
.gridCellColumns(span)
}
}
}
}
}カスタムコンテナでの活用
// カスタム値の定義
extension ContainerValues {
@Entry var priority: Priority = .normal
@Entry var isHighlighted: Bool = false
}
enum Priority: Int, Comparable {
case low, normal, high
static func < (lhs: Priority, rhs: Priority) -> Bool {
lhs.rawValue < rhs.rawValue
}
}
// 子Viewで設定
Text("重要なメッセージ")
.containerValue(\.priority, .high)
.containerValue(\.isHighlighted, true)
// 親コンテナで読み取り・ソート
struct PriorityList: View {
@ViewBuilder var content: some View
var body: some View {
VStack {
ForEach(subviewOf: content) { subview in
let priority = subview.containerValues.priority
let highlighted = subview.containerValues.isHighlighted
subview
.background(highlighted ? Color.yellow.opacity(0.3) : Color.clear)
}
}
}
}Preference Key との比較
| 特徴 | ContainerValues | PreferenceKey |
|---|---|---|
| 方向 | 子 → 直接の親コンテナ | 子 → 祖先全体 |
| 型安全 | @Entryマクロで簡潔 | 手動でプロトコル準拠 |
| 用途 | レイアウト情報、構成 | 集約値(サイズ、アンカーなど) |
| iOS | 18+ | 13+ |
---
Transaction (iOS 18+)
アニメーションのコンテキスト自体を操作。親から伝播するアニメーションの無効化や上書きが可能。
基本的なTransaction
struct ContentView: View {
@State private var isExpanded = false
var body: some View {
VStack {
Button("Toggle") {
// withAnimationの代わりにTransaction
var transaction = Transaction(animation: .spring(response: 0.3))
transaction.disablesAnimations = false
withTransaction(transaction) {
isExpanded.toggle()
}
}
Rectangle()
.frame(width: isExpanded ? 200 : 100)
}
}
}アニメーションの無効化
親から伝播してきたアニメーションを子で無効化。
struct ChildView: View {
let value: Double
var body: some View {
Text("\(value, specifier: "%.1f")")
.transaction { transaction in
// 親からのアニメーションを無効化
transaction.disablesAnimations = true
}
}
}
// 親View
struct ParentView: View {
@State private var value = 0.0
var body: some View {
VStack {
// このアニメーションはChildViewには適用されない
Slider(value: $value)
.animation(.easeInOut, value: value)
ChildView(value: value)
}
}
}アニメーションの上書き
struct ContentView: View {
@State private var isOn = false
var body: some View {
VStack {
Toggle("Toggle", isOn: $isOn)
Circle()
.frame(width: isOn ? 100 : 50)
.transaction { transaction in
// 親のアニメーションを上書き
transaction.animation = .bouncy(duration: 0.5)
}
}
.animation(.linear, value: isOn) // このアニメーションは上書きされる
}
}条件付きアニメーション
struct SmartAnimationView: View {
@State private var count = 0
@Environment(\.accessibilityReduceMotion) var reduceMotion
var body: some View {
Text("\(count)")
.font(.largeTitle)
.transaction { transaction in
if reduceMotion {
transaction.disablesAnimations = true
} else {
transaction.animation = .spring(response: 0.3)
}
}
Button("Increment") {
count += 1
}
}
}特定のビューのみアニメーション
struct SelectiveAnimationView: View {
@State private var isExpanded = false
var body: some View {
VStack {
// このビューはアニメーションする
Rectangle()
.frame(height: isExpanded ? 200 : 100)
.animation(.spring, value: isExpanded)
// このビューはアニメーションしない
Text("Status: \(isExpanded ? "Expanded" : "Collapsed")")
.transaction { $0.disablesAnimations = true }
Button("Toggle") {
isExpanded.toggle()
}
}
}
}---
関連ドキュメント
レイアウトコンポーネント
バージョン対応表
| コンポーネント | iOS | iPadOS | macOS | 備考 |
|---|---|---|---|---|
| Grid | 16+ | 16+ | 13+ | 柔軟なグリッドレイアウト |
| GridRow | 16+ | 16+ | 13+ | グリッド行 |
| ViewThatFits | 16+ | 16+ | 13+ | 自動ビュー選択 |
| AnyLayout | 16+ | 16+ | 13+ | 動的レイアウト切り替え |
| Gauge | 16+ | 16+ | 13+ | 進捗・レベル表示 |
| Table | 16+ | 16+ | 12+ | 表形式データ(主にmacOS/iPadOS) |
| TextField(axis:) | 16+ | 16+ | 13+ | 複数行テキストフィールド |
| ToolbarSpacer | 26+ | 26+ | 26+ | ツールバースペース制御 |
| labelIconToTitleSpacing | 26+ | 26+ | 26+ | Labelスペース調整 |
---
Grid (iOS 16+)
より柔軟なグリッドレイアウト。LazyVGrid/LazyHGridと異なり、すべてのセルが一度にレンダリングされる。
基本的な使用法
Grid {
GridRow {
Text("1")
Text("2")
Text("3")
}
GridRow {
Text("4")
Text("5")
Text("6")
}
}複数列にまたがるセル
Grid {
GridRow {
Text("1")
Text("2")
Text("3")
}
GridRow {
Text("4")
Text("5")
.gridCellColumns(2) // 2列分を占有
}
Divider()
.gridCellUnsizedAxes(.horizontal)
GridRow {
Text("7")
Text("8")
Text("9")
}
}アライメント
Grid(alignment: .leading, horizontalSpacing: 10, verticalSpacing: 10) {
GridRow {
Text("Name:")
.gridColumnAlignment(.trailing)
Text("John Doe")
}
GridRow {
Text("Email:")
Text("john@example.com")
}
}---
ViewThatFits (iOS 16+)
利用可能なスペースに応じてビューを自動選択。
基本的な使用法
ViewThatFits {
// 優先度順に試行
HStack {
Image(systemName: "star")
Text("お気に入りに追加")
}
// スペースが足りない場合
HStack {
Image(systemName: "star")
Text("追加")
}
// さらにスペースが足りない場合
Image(systemName: "star")
}軸を指定
ViewThatFits(in: .horizontal) {
// 水平方向のスペースのみをチェック
WideView()
NarrowView()
}---
AnyLayout (iOS 16+)
条件に応じてレイアウトを動的に切り替え。
基本的な使用法
struct AdaptiveView: View {
@Environment(\.horizontalSizeClass) var sizeClass
var layout: AnyLayout {
sizeClass == .compact
? AnyLayout(VStackLayout())
: AnyLayout(HStackLayout())
}
var body: some View {
layout {
Image(systemName: "photo")
Text("タイトル")
Text("説明文")
}
}
}アニメーション付きレイアウト変更
struct AnimatedLayoutView: View {
@State private var isGrid = false
var layout: AnyLayout {
isGrid
? AnyLayout(GridLayout())
: AnyLayout(VStackLayout(spacing: 10))
}
var body: some View {
layout {
ForEach(items) { item in
ItemView(item: item)
}
}
.animation(.spring(), value: isGrid)
Toggle("Grid View", isOn: $isGrid)
}
}---
Gauge (iOS 16+)
進捗やレベルを視覚的に表示。
基本的なゲージ
Gauge(value: 0.7) {
Text("進捗")
}範囲とラベル付き
Gauge(value: 75, in: 0...100) {
Text("バッテリー")
} currentValueLabel: {
Text("75%")
} minimumValueLabel: {
Text("0")
} maximumValueLabel: {
Text("100")
}スタイル
// 線形
Gauge(value: 0.5) {
Text("レベル")
}
.gaugeStyle(.linearCapacity)
// 円形アクセサリ
Gauge(value: 0.5) {
Text("レベル")
}
.gaugeStyle(.accessoryCircular)
// その他のスタイル
// .accessoryLinear
// .accessoryCircularCapacityカスタムゲージ
Gauge(value: progress) {
Image(systemName: "heart.fill")
} currentValueLabel: {
Text("\(Int(progress * 100))%")
}
.tint(Gradient(colors: [.green, .yellow, .red]))---
Table (iOS 16+)
表形式のデータ表示。主にmacOSとiPadOS向け。
基本的な使用法
struct Person: Identifiable {
let id = UUID()
var name: String
var age: Int
var email: String
}
struct PeopleTable: View {
@State private var people: [Person] = [...]
@State private var selection: Set<Person.ID> = []
@State private var sortOrder = [KeyPathComparator(\Person.name)]
var body: some View {
Table(people, selection: $selection, sortOrder: $sortOrder) {
TableColumn("名前", value: \.name)
TableColumn("年齢") { person in
Text("\(person.age)")
}
TableColumn("メール", value: \.email)
}
.onChange(of: sortOrder) { newOrder in
people.sort(using: newOrder)
}
}
}---
TextField改善 (iOS 16+)
軸方向の拡張
TextField("メッセージ", text: $message, axis: .vertical)
.lineLimit(3...6) // 3〜6行で自動拡張フォーカス制御の強化
enum Field: Hashable {
case username, password
}
struct LoginForm: View {
@State private var username = ""
@State private var password = ""
@FocusState private var focusedField: Field?
var body: some View {
Form {
TextField("ユーザー名", text: $username)
.focused($focusedField, equals: .username)
.submitLabel(.next)
.onSubmit {
focusedField = .password
}
SecureField("パスワード", text: $password)
.focused($focusedField, equals: .password)
.submitLabel(.done)
}
}
}defaultFocus (iOS 17+)
struct FormView: View {
@State private var email = ""
@State private var password = ""
@FocusState private var focusedField: Field?
var body: some View {
Form {
TextField("メール", text: $email)
.focused($focusedField, equals: .email)
SecureField("パスワード", text: $password)
.focused($focusedField, equals: .password)
}
.defaultFocus($focusedField, .email) // 初期フォーカス
}
}---
ToolbarSpacer (iOS 26+)
ツールバーアイテム間のスペース制御。
struct ContentView: View {
var body: some View {
NavigationStack {
Content()
.toolbar {
ToolbarItemGroup(placement: .primaryAction) {
Button("Action 1") { }
ToolbarSpacer()
Button("Action 2") { }
}
}
}
}
}---
labelIconToTitleSpacing (iOS 26+)
Labelのアイコンとテキスト間のスペース調整。
Label("Hello", systemImage: "globe")
.labelIconToTitleSpacing(8)
Label("Settings", systemImage: "gear")
.labelIconToTitleSpacing(12)---
関連ドキュメント
メディア・共有コンポーネント
バージョン対応表
| コンポーネント | iOS | iPadOS | macOS | 備考 |
|---|---|---|---|---|
| PhotosPicker | 16+ | 16+ | 13+ | 権限不要の写真選択 |
| ShareLink | 16+ | 16+ | 13+ | 標準シェア機能 |
| Transferable | 16+ | 16+ | 13+ | データ転送プロトコル |
---
PhotosPicker (iOS 16+)
写真ライブラリへのアクセス権限なしで写真を選択可能。
基本的な使用法
import PhotosUI
struct PhotoSelectorView: View {
@State private var selectedItem: PhotosPickerItem?
@State private var selectedImage: Image?
var body: some View {
VStack {
if let image = selectedImage {
image
.resizable()
.scaledToFit()
}
PhotosPicker(
selection: $selectedItem,
matching: .images
) {
Label("写真を選択", systemImage: "photo")
}
}
.onChange(of: selectedItem) { newItem in
Task {
if let data = try? await newItem?.loadTransferable(type: Data.self),
let uiImage = UIImage(data: data) {
selectedImage = Image(uiImage: uiImage)
}
}
}
}
}複数選択
@State private var selectedItems: [PhotosPickerItem] = []
@State private var selectedImages: [Image] = []
PhotosPicker(
selection: $selectedItems,
maxSelectionCount: 5,
matching: .images
) {
Text("最大5枚選択")
}
.onChange(of: selectedItems) { newItems in
Task {
selectedImages = []
for item in newItems {
if let data = try? await item.loadTransferable(type: Data.self),
let uiImage = UIImage(data: data) {
selectedImages.append(Image(uiImage: uiImage))
}
}
}
}フィルタリング
// 画像のみ(スクリーンショット除外)
PhotosPicker(
selection: $selectedItems,
matching: .any(of: [.images, .not(.screenshots)])
) { ... }
// 動画のみ
PhotosPicker(
selection: $selectedItems,
matching: .videos
) { ... }
// ライブフォト
PhotosPicker(
selection: $selectedItems,
matching: .livePhotos
) { ... }
// 画像と動画
PhotosPicker(
selection: $selectedItems,
matching: .any(of: [.images, .videos])
) { ... }---
ShareLink (iOS 16+)
標準のシェア機能をSwiftUIで簡単に実装。
基本的な使用法
// URL共有
ShareLink(item: URL(string: "https://example.com")!)
// カスタムラベル
ShareLink(item: article.url) {
Label("共有", systemImage: "square.and.arrow.up")
}
// プレビュー付き
ShareLink(
item: photo,
subject: Text("写真を共有"),
message: Text("この写真を見てください"),
preview: SharePreview(
photo.title,
image: photo.image
)
)Transferableプロトコル
カスタム型をシェア可能にする。
struct Article: Transferable {
var title: String
var body: String
var url: URL
static var transferRepresentation: some TransferRepresentation {
ProxyRepresentation(exporting: \.url)
}
}
// 使用
ShareLink(item: article, preview: SharePreview(article.title))複数のTransferRepresentation
struct Document: Transferable {
var title: String
var content: String
var pdfData: Data
static var transferRepresentation: some TransferRepresentation {
// PDF形式
DataRepresentation(exportedContentType: .pdf) { document in
document.pdfData
}
// プレーンテキスト形式
CodableRepresentation(contentType: .plainText)
// URL形式(フォールバック)
ProxyRepresentation(exporting: \.shareURL)
}
var shareURL: URL {
URL(string: "https://example.com/doc/\(title)")!
}
}---
移行チェックリスト
PhotosPicker導入
1. [ ] import PhotosUIを追加 2. [ ] PHPhotoLibraryの許可リクエストが不要に 3. [ ] UIImagePickerControllerを置換 4. [ ] 非同期で画像データをロード
ShareLink導入
1. [ ] 共有するデータ型をTransferableに準拠 2. [ ] 適切なプレビューを提供 3. [ ] UIActivityViewControllerを置換
---
関連ドキュメント
ナビゲーションコンポーネント
バージョン対応表
| コンポーネント | iOS | iPadOS | macOS | 備考 |
|---|---|---|---|---|
| NavigationStack | 16+ | 16+ | 13+ | NavigationViewの後継 |
| NavigationSplitView | 16+ | 16+ | 13+ | 2列/3列レイアウト |
| NavigationPath | 16+ | 16+ | 13+ | 複数型のナビゲーションパス |
| navigationDestination | 16+ | 16+ | 13+ | 宣言的な遷移先定義 |
| TabView (iOS 26刷新) | 26+ | 26+ | - | 新デザイン・アニメーション |
---
NavigationStack
iOS 16で導入されたNavigationViewの後継。プログラマティックなナビゲーション制御が可能。
基本的な使用法
// iOS 15以前: NavigationView(非推奨)
NavigationView {
List(items) { item in
NavigationLink(destination: DetailView(item: item)) {
Text(item.name)
}
}
}
// iOS 16+: NavigationStack
NavigationStack {
List(items) { item in
NavigationLink(value: item) {
Text(item.name)
}
}
.navigationDestination(for: Item.self) { item in
DetailView(item: item)
}
}プログラマティックナビゲーション
struct ContentView: View {
@State private var path: [Item] = []
var body: some View {
NavigationStack(path: $path) {
List(items) { item in
NavigationLink(value: item) {
Text(item.name)
}
}
.navigationDestination(for: Item.self) { item in
DetailView(item: item)
}
}
}
// プログラムでナビゲーション
func navigateTo(_ item: Item) {
path.append(item)
}
// ルートに戻る
func popToRoot() {
path.removeAll()
}
}NavigationPath(複数の型をサポート)
struct ContentView: View {
@State private var path = NavigationPath()
var body: some View {
NavigationStack(path: $path) {
VStack {
Button("Show User") {
path.append(User(name: "John"))
}
Button("Show Article") {
path.append(Article(title: "News"))
}
}
.navigationDestination(for: User.self) { user in
UserDetailView(user: user)
}
.navigationDestination(for: Article.self) { article in
ArticleDetailView(article: article)
}
}
}
}---
NavigationSplitView
2列または3列のレイアウトを提供。iPadやMacでのマスター・ディテール表示に最適。
2列レイアウト
NavigationSplitView {
// サイドバー
List(categories, selection: $selectedCategory) { category in
Text(category.name)
}
} detail: {
// 詳細ビュー
if let category = selectedCategory {
CategoryDetailView(category: category)
} else {
Text("カテゴリを選択してください")
}
}3列レイアウト
NavigationSplitView {
// サイドバー
CategoryList(selection: $selectedCategory)
} content: {
// コンテンツ列
if let category = selectedCategory {
ItemList(category: category, selection: $selectedItem)
}
} detail: {
// 詳細列
if let item = selectedItem {
ItemDetailView(item: item)
}
}カラム幅のカスタマイズ
NavigationSplitView(columnVisibility: $columnVisibility) {
Sidebar()
} content: {
Content()
} detail: {
Detail()
}
.navigationSplitViewColumnWidth(min: 180, ideal: 200, max: 250)---
後方互換性
iOS 15以前をサポートする場合:
var body: some View {
if #available(iOS 16.0, *) {
NavigationStack {
content
}
} else {
NavigationView {
content
}
.navigationViewStyle(.stack)
}
}---
TabView刷新 (iOS 26+)
iOS 26ではiPhoneのTabViewのUI/UXが刷新。
特徴
- 新しいビジュアルデザイン(Liquid Glassとの統合)
- スムーズなアニメーション
- よりモダンなタブ切り替え体験
基本的な使用法
TabView {
HomeView()
.tabItem {
Label("ホーム", systemImage: "house")
}
SearchView()
.tabItem {
Label("検索", systemImage: "magnifyingglass")
}
ProfileView()
.tabItem {
Label("プロフィール", systemImage: "person")
}
}注意: iOS 26では標準のTabViewが自動的に新デザインに更新されます。特別な対応は不要ですが、カスタマイズしている場合は確認が必要です。
---
移行チェックリスト
NavigationViewからの移行
1. [ ] NavigationViewをNavigationStackまたはNavigationSplitViewに置換 2. [ ] NavigationLink(destination:)をNavigationLink(value:)に変更 3. [ ] .navigationDestination(for:)を追加 4. [ ] プログラマティックナビゲーションには@Stateでパスを管理 5. [ ] .navigationViewStyle(.stack)を削除(NavigationStackはデフォルトでスタック)
---
関連ドキュメント
プレビューコンポーネント
バージョン対応表
| コンポーネント | iOS | iPadOS | macOS | Xcode | 備考 |
|---|---|---|---|---|---|
| #Preview | 17+ | 17+ | 14+ | 15+ | 簡略化されたプレビューマクロ |
| @Previewable | 18+ | 18+ | 15+ | 16+ | プレビュー内で@State使用可能 |
---
#Preview マクロ (iOS 17+)
従来のPreviewProviderプロトコルを置き換える簡略化されたプレビュー構文。
基本的な使用法
// iOS 16以前: PreviewProvider
struct ArticleView_Previews: PreviewProvider {
static var previews: some View {
ArticleView(article: .sample)
}
}
// iOS 17+: #Previewマクロ
#Preview {
ArticleView(article: .sample)
}名前付きプレビュー
#Preview("通常状態") {
ContentView(state: .normal)
}
#Preview("エラー状態") {
ContentView(state: .error)
}
#Preview("ローディング状態") {
ContentView(state: .loading)
}ダークモード
#Preview("ダークモード") {
ArticleView(article: .sample)
.preferredColorScheme(.dark)
}デバイス回転
#Preview(traits: .landscapeLeft) {
ArticleView(article: .sample)
}
#Preview(traits: .portrait) {
ArticleView(article: .sample)
}固定サイズ
#Preview(traits: .fixedLayout(width: 300, height: 200)) {
CardView(card: .sample)
}複数のトレイト
#Preview(traits: .landscapeLeft, .sizeThatFitsLayout) {
MyView()
}---
@Previewable (iOS 18+ / Xcode 16+)
プレビュー内で直接@Stateを使用可能に。ラッパーViewが不要。
基本的な使用法
// iOS 17以前: ラッパーViewが必要だった
struct TogglePreviewWrapper: View {
@State private var isOn = false
var body: some View {
MyToggle(isOn: $isOn)
}
}
#Preview {
TogglePreviewWrapper()
}
// iOS 18 / Xcode 16: @Previewableで直接定義
#Preview {
@Previewable @State var isOn = false
MyToggle(isOn: $isOn)
}複数の状態を持つプレビュー
#Preview("Form Preview") {
@Previewable @State var name = ""
@Previewable @State var email = ""
@Previewable @State var isSubscribed = true
Form {
TextField("Name", text: $name)
TextField("Email", text: $email)
Toggle("Subscribe", isOn: $isSubscribed)
}
}インタラクティブなプレビュー
#Preview("Counter") {
@Previewable @State var count = 0
VStack(spacing: 20) {
Text("Count: \(count)")
.font(.largeTitle)
HStack {
Button("-") { count -= 1 }
Button("+") { count += 1 }
}
.buttonStyle(.borderedProminent)
}
}環境値との組み合わせ
#Preview("Dark Mode") {
@Previewable @State var text = ""
TextField("Input", text: $text)
.preferredColorScheme(.dark)
}
#Preview("Large Text") {
@Previewable @State var isOn = false
Toggle("Option", isOn: $isOn)
.environment(\.sizeCategory, .accessibilityExtraLarge)
}スライダー付きプレビュー
#Preview("Adjustable") {
@Previewable @State var progress: Double = 0.5
VStack {
ProgressView(value: progress)
Slider(value: $progress)
}
.padding()
}---
プレビューのベストプラクティス
サンプルデータの用意
extension Article {
static var sample: Article {
Article(
id: UUID(),
title: "サンプル記事",
content: "これはプレビュー用のサンプルコンテンツです。"
)
}
}
#Preview {
ArticleView(article: .sample)
}複数状態のプレビュー
#Preview("Empty") {
ListView(items: [])
}
#Preview("Few Items") {
ListView(items: Array(repeating: .sample, count: 3))
}
#Preview("Many Items") {
ListView(items: Array(repeating: .sample, count: 50))
}環境設定のバリエーション
#Preview("Default") {
MyView()
}
#Preview("Dark Mode") {
MyView()
.preferredColorScheme(.dark)
}
#Preview("RTL") {
MyView()
.environment(\.layoutDirection, .rightToLeft)
}
#Preview("Large Dynamic Type") {
MyView()
.environment(\.sizeCategory, .accessibilityLarge)
}---
関連ドキュメント
スクロールコンポーネント
バージョン対応表
| コンポーネント | iOS | iPadOS | macOS | 備考 |
|---|---|---|---|---|
| scrollPosition | 17+ | 17+ | 14+ | スクロール位置の取得・設定 |
| scrollTargetBehavior | 17+ | 17+ | 14+ | スナップスクロール |
| scrollTargetLayout | 17+ | 17+ | 14+ | スクロールターゲットレイアウト |
| containerRelativeFrame | 17+ | 17+ | 14+ | 親コンテナ相対サイズ |
| scrollIndicators | 16+ | 16+ | 13+ | スクロールインジケータ制御 |
| scrollClipDisabled | 17+ | 17+ | 14+ | クリッピング無効化 |
| safeAreaPadding | 17+ | 17+ | 14+ | 安全領域余白 |
---
scrollPosition (iOS 17+)
スクロール位置の取得と設定を可能にする。
基本的な使用法
struct ArticleList: View {
@State private var scrollPosition: Int?
let articles: [Article]
var body: some View {
ScrollView {
LazyVStack {
ForEach(articles) { article in
ArticleRow(article: article)
.id(article.id)
}
}
.scrollTargetLayout()
}
.scrollPosition(id: $scrollPosition)
// 特定の位置にスクロール
Button("先頭へ") {
withAnimation {
scrollPosition = articles.first?.id
}
}
}
}String IDでの使用
struct ScrollableList: View {
@State private var scrollPosition: String?
let items = (1...100).map { "Item \($0)" }
var body: some View {
VStack {
// 現在位置の表示
Text("Current: \(scrollPosition ?? "none")")
ScrollView {
LazyVStack {
ForEach(items, id: \.self) { item in
Text(item)
.frame(height: 50)
}
}
.scrollTargetLayout()
}
.scrollPosition(id: $scrollPosition)
// プログラムでスクロール
Button("Go to Item 50") {
withAnimation {
scrollPosition = "Item 50"
}
}
}
}
}---
scrollTargetBehavior (iOS 17+)
スクロールのスナップ動作を制御。
ビュー単位のスナップ
ScrollView(.horizontal) {
LazyHStack(spacing: 16) {
ForEach(cards) { card in
CardView(card: card)
.containerRelativeFrame(.horizontal)
}
}
.scrollTargetLayout()
}
.scrollTargetBehavior(.viewAligned) // ビュー単位でスナップページング
ScrollView(.horizontal) {
LazyHStack(spacing: 0) {
ForEach(pages) { page in
PageView(page: page)
.containerRelativeFrame(.horizontal)
}
}
.scrollTargetLayout()
}
.scrollTargetBehavior(.paging) // ページング動作---
containerRelativeFrame (iOS 17+)
親コンテナに対する相対的なサイズ指定。
基本的な使用法
ScrollView(.horizontal) {
LazyHStack {
ForEach(items) { item in
ItemView(item: item)
.containerRelativeFrame(.horizontal, count: 3, spacing: 8)
// 画面幅を3分割したサイズ
}
}
}カード表示例
ScrollView(.horizontal) {
LazyHStack(spacing: 16) {
ForEach(cards) { card in
CardView(card: card)
.containerRelativeFrame(.horizontal) { width, _ in
width * 0.85 // 画面幅の85%
}
}
}
.scrollTargetLayout()
}
.scrollTargetBehavior(.viewAligned)
.contentMargins(.horizontal, 16, for: .scrollContent)---
scrollIndicators (iOS 16+)
スクロールインジケータの表示制御。
ScrollView {
content
}
.scrollIndicators(.hidden) // スクロールインジケータを非表示
.scrollIndicators(.visible, axes: .vertical) // 垂直のみ表示
.scrollIndicators(.automatic) // 自動(デフォルト)---
scrollClipDisabled (iOS 17+)
スクロールビューのクリッピングを無効化。シャドウやオーバーフロー要素を表示可能に。
ScrollView(.horizontal) {
LazyHStack {
ForEach(cards) { card in
CardView(card: card)
.shadow(radius: 10) // シャドウがクリップされない
}
}
.padding(.vertical, 20)
}
.scrollClipDisabled()---
safeAreaPadding (iOS 17+)
安全領域に余白を追加。フローティングUIの配置に便利。
ScrollView {
content
}
.safeAreaPadding(.bottom, 80) // 下部にフローティングボタン用のスペース
// 全方向
.safeAreaPadding(20)
// 特定の辺
.safeAreaPadding(.horizontal, 16)---
後方互換性
iOS 16以前をサポートする場合:
var body: some View {
if #available(iOS 17.0, *) {
content
.scrollPosition(id: $position)
} else {
ScrollViewReader { proxy in
content
.onChange(of: targetId) { id in
proxy.scrollTo(id)
}
}
}
}---
関連ドキュメント
テーマ・デザインコンポーネント
バージョン対応表
| コンポーネント | iOS | iPadOS | macOS | 備考 |
|---|---|---|---|---|
| Liquid Glass | 26+ | 26+ | 26+ | iOS 7以来最大のUI刷新 |
| .liquidGlass() | 26+ | 26+ | 26+ | Liquid Glassエフェクト |
| .depthLayer() | 26+ | 26+ | 26+ | 深度レイヤー設定 |
| .adaptiveTint() | 26+ | 26+ | 26+ | 適応型ティント |
---
Liquid Glass (iOS 26+)
iOS 7以来最大のUI刷新。visionOSにインスパイアされた半透明・ガラス風デザイン。
概要
- 光、コンテンツ、入力に反応する動的なUI要素
- 標準UIKit/SwiftUIコンポーネントは自動的に新デザインに適用
- カスタムUIは調整が必要な場合あり
基本的な使用法
// Liquid Glass エフェクトの適用
View()
.liquidGlass(.prominent)
.depthLayer(.background)
.adaptiveTint(.system)スタイルオプション
// 目立つスタイル
.liquidGlass(.prominent)
// 控えめなスタイル
.liquidGlass(.subtle)
// カスタムティント
.liquidGlass(.prominent)
.tint(.blue)深度レイヤー
// 背景レイヤー
.depthLayer(.background)
// 前面レイヤー
.depthLayer(.foreground)
// オーバーレイレイヤー
.depthLayer(.overlay)---
移行オプション
猶予期間
- Xcode 26では1年間の猶予期間あり
- Liquid Glassを無効化して従来UIを維持可能
- 段階的な移行を推奨
無効化方法
// Info.plistで設定
// UIDisableLiquidGlass = YES---
デザインガイドライン
推奨事項
- 全面画像背景の活用を推奨
- コンテンツの読みやすさを確保
- 既存のブラー効果との競合に注意
注意点
- カスタムビューの透明度やブラー効果を見直す必要がある可能性
- 既存のカスタムUIがLiquid Glassと調和するか確認
- ダークモード・ライトモード両方でテスト
自動適用されるコンポーネント
以下の標準コンポーネントは自動的にLiquid Glassスタイルが適用されます:
- NavigationBar
- TabBar
- ToolBar
- Alert
- ActionSheet
- Sheets
---
後方互換性
var body: some View {
if #available(iOS 26.0, *) {
content
.liquidGlass(.prominent)
} else {
content
.background(.ultraThinMaterial)
}
}---
関連ドキュメント
TipKit
バージョン対応表
| コンポーネント | iOS | iPadOS | macOS | 備考 |
|---|---|---|---|---|
| TipKit | 17+ | 17+ | 14+ | 機能発見ヒント表示 |
| TipView | 17+ | 17+ | 14+ | インラインTip表示 |
| popoverTip | 17+ | 17+ | 14+ | ポップオーバーTip表示 |
| Tip Rules | 17+ | 17+ | 14+ | 表示条件制御 |
---
概要
TipKitは、アプリの機能発見を促進するヒント表示フレームワーク。ユーザーに機能を教えるための統一されたUIを提供。
---
基本的なTip定義
import TipKit
struct FavoriteTip: Tip {
var title: Text {
Text("お気に入りに追加")
}
var message: Text? {
Text("星をタップして記事をお気に入りに保存できます")
}
var image: Image? {
Image(systemName: "star")
}
}---
Tipの表示
インラインTip(TipView)
struct ArticleView: View {
let favoriteTip = FavoriteTip()
var body: some View {
VStack {
TipView(favoriteTip)
// コンテンツ
ArticleContent()
}
}
}ポップオーバーTip
struct ArticleView: View {
let favoriteTip = FavoriteTip()
var body: some View {
Button(action: addToFavorites) {
Image(systemName: "star")
}
.popoverTip(favoriteTip)
}
}---
TipKitの設定
アプリ起動時に設定が必要。
@main
struct MyApp: App {
var body: some Scene {
WindowGroup {
ContentView()
.task {
try? Tips.configure([
.displayFrequency(.immediate),
.datastoreLocation(.applicationDefault)
])
}
}
}
}表示頻度オプション
// 即時表示
.displayFrequency(.immediate)
// 1時間ごと
.displayFrequency(.hourly)
// 1日ごと
.displayFrequency(.daily)
// 1週間ごと
.displayFrequency(.weekly)
// 1ヶ月ごと
.displayFrequency(.monthly)---
Tipのルールと条件
イベントベースのルール
ユーザーのアクション回数に基づいて表示。
struct AdvancedTip: Tip {
// イベント定義
static let articleViewedEvent = Event(id: "articleViewed")
var rules: [Rule] {
// 3回以上記事を見た後に表示
#Rule(Self.articleViewedEvent) { $0.donations.count >= 3 }
}
var title: Text {
Text("もっと便利に")
}
}
// イベントの発火
Button("記事を見る") {
AdvancedTip.articleViewedEvent.donate()
}パラメータベースのルール
アプリの状態に基づいて表示。
struct LoginRequiredTip: Tip {
@Parameter
static var isLoggedIn: Bool = false
var rules: [Rule] {
#Rule(Self.$isLoggedIn) { $0 == true }
}
var title: Text {
Text("プレミアム機能")
}
}
// パラメータの更新
func onLogin() {
LoginRequiredTip.isLoggedIn = true
}複合ルール
struct ComplexTip: Tip {
static let usageEvent = Event(id: "usage")
@Parameter
static var isPremiumUser: Bool = false
var rules: [Rule] {
#Rule(Self.usageEvent) { $0.donations.count >= 5 }
#Rule(Self.$isPremiumUser) { $0 == true }
}
var title: Text {
Text("上級者向け機能")
}
}---
Tipの無効化
手動で無効化
let favoriteTip = FavoriteTip()
// ユーザーがアクションを実行した時
func addToFavorites() {
// 処理
favoriteTip.invalidate(reason: .actionPerformed)
}無効化理由
// ユーザーがアクションを実行
.actionPerformed
// ユーザーがTipを閉じた
.tipClosed
// 最大表示回数に達した
.maxDisplayCountExceededデータストアのリセット(開発用)
// 全てのTipをリセット
try? Tips.resetDatastore()---
カスタマイズ
TipViewのスタイル
TipView(favoriteTip, arrowEdge: .top)
TipView(favoriteTip)
.tipBackground(Color.blue.opacity(0.1))アクション付きTip
struct ActionTip: Tip {
var title: Text {
Text("新機能")
}
var message: Text? {
Text("この機能を試してみませんか?")
}
var actions: [Action] {
Action(id: "try", title: "試してみる")
Action(id: "later", title: "後で")
}
}
// アクションの処理
TipView(actionTip) { action in
if action.id == "try" {
// 機能を実行
}
actionTip.invalidate(reason: .actionPerformed)
}---
移行チェックリスト
TipKit導入
1. [ ] import TipKitを追加 2. [ ] Tips.configure()をアプリ起動時に呼び出し 3. [ ] Tip構造体を定義 4. [ ] TipViewまたは.popoverTip()で表示 5. [ ] 適切なタイミングでinvalidate()を呼び出し
---
関連ドキュメント
WebViewコンポーネント
バージョン対応表
| コンポーネント | iOS | iPadOS | macOS | 備考 |
|---|---|---|---|---|
| WebView | 26+ | 26+ | 26+ | SwiftUIネイティブWebView |
---
WebView (iOS 26+)
SwiftUIネイティブのWebView。従来のWKWebViewラッパーが不要に。
基本的な使用法
import SwiftUI
struct WebContent: View {
var body: some View {
WebView(url: URL(string: "https://example.com")!)
}
}ナビゲーション制御
WebView(url: url)
.webViewAllowsBackForwardNavigation(true)リーダーモード
WebView(url: url)
.webViewReaderMode(enabled: true)カスタマイズ例
struct BrowserView: View {
@State private var url = URL(string: "https://apple.com")!
@State private var isLoading = false
var body: some View {
VStack {
if isLoading {
ProgressView()
}
WebView(url: url)
.webViewAllowsBackForwardNavigation(true)
.onWebViewStartLoading {
isLoading = true
}
.onWebViewFinishLoading {
isLoading = false
}
}
}
}---
Scene Bridging (iOS 26+)
UIKit/AppKitアプリにSwiftUIシーンを統合。完全な書き換え不要で段階的移行が可能。
ユースケース
- 既存UIKitアプリへの新機能追加
- 段階的なSwiftUI移行
- 複数ウィンドウ対応
概念
// 既存のUIKitアプリにSwiftUIシーンを追加
// SceneDelegateやAppDelegateからSwiftUIシーンを起動可能---
従来のWKWebViewラッパー(iOS 25以前)
iOS 26未満をサポートする必要がある場合:
import SwiftUI
import WebKit
struct LegacyWebView: UIViewRepresentable {
let url: URL
func makeUIView(context: Context) -> WKWebView {
WKWebView()
}
func updateUIView(_ webView: WKWebView, context: Context) {
let request = URLRequest(url: url)
webView.load(request)
}
}後方互換性
var body: some View {
if #available(iOS 26.0, *) {
WebView(url: url)
} else {
LegacyWebView(url: url)
}
}---