Add branding system with customizable launch screen and app icon

- Add AppIconView with configurable title, subtitle, icon, and colors
- Add LaunchScreenView with multiple pattern styles (dots, grid, radial, none)
- Add layout styles (iconAboveTitle, titleAboveIcon, iconOnly, titleOnly)
- Add IconGeneratorView for exporting 1024px app icons
- Add BrandingPreviewView for previewing branding assets
- Add AppLaunchView wrapper for animated app launch
- Add comprehensive BRANDING_GUIDE.md documentation
- Remove all casino-specific references, make fully generic
This commit is contained in:
Matt Bruce 2026-01-04 14:38:32 -06:00
parent 9212cd4c23
commit c7c507c8f7
8 changed files with 1888 additions and 0 deletions

View File

@ -0,0 +1,231 @@
//
// AppIconView.swift
// Bedrock
//
// A reusable app icon design that can be customized for any app.
// Render this view to an image for use as your app icon.
//
import SwiftUI
/// Configuration for the app icon appearance.
public struct AppIconConfig: Sendable {
public let title: String
public let subtitle: String?
public let iconSymbol: String
public let primaryColor: Color
public let secondaryColor: Color
public let accentColor: Color
public init(
title: String,
subtitle: String? = nil,
iconSymbol: String,
primaryColor: Color = Color(red: 0.15, green: 0.20, blue: 0.30),
secondaryColor: Color = Color(red: 0.08, green: 0.10, blue: 0.18),
accentColor: Color = Color(red: 0.4, green: 0.7, blue: 1.0)
) {
self.title = title
self.subtitle = subtitle
self.iconSymbol = iconSymbol
self.primaryColor = primaryColor
self.secondaryColor = secondaryColor
self.accentColor = accentColor
}
// MARK: - Example Configuration (for previews only)
/// Example configuration for Bedrock previews.
/// Apps should define their own configs in `BrandingConfig.swift`.
public static let example = AppIconConfig(
title: "MY APP",
iconSymbol: "star.fill"
)
}
/// A customizable app icon view for any app.
/// Render this view to create your app icon assets.
///
/// **Important**: This view generates a full-bleed square icon. iOS applies its own
/// superellipse mask, so decorative borders are inset to avoid clipping at the edges.
public struct AppIconView: View {
let config: AppIconConfig
let size: CGFloat
public init(config: AppIconConfig, size: CGFloat = 1024) {
self.config = config
self.size = size
}
// Size calculations
private var iconSize: CGFloat { size * 0.35 }
private var subtitleSize: CGFloat { size * 0.25 }
/// Dynamic title size based on text length.
/// Shorter titles get larger fonts, longer titles shrink to fit within the border.
private var titleSize: CGFloat {
let baseSize = size * 0.12
let length = config.title.count
// Scale factor: full size for 6 chars, progressively smaller for longer
let scaleFactor: CGFloat = switch length {
case ...6: 1.0 // "SELFIE", "CAMERA"
case 7: 0.95 // "WEATHER"
case 8: 0.85 // "SETTINGS"
case 9: 0.75 // "MESSENGER"
default: 0.65 // Very long titles
}
return baseSize * scaleFactor
}
public var body: some View {
ZStack {
// Background gradient - full bleed, no rounded corners
// iOS will apply its own superellipse mask
Rectangle()
.fill(
LinearGradient(
colors: [config.primaryColor, config.secondaryColor],
startPoint: .topLeading,
endPoint: .bottomTrailing
)
)
// Subtle pattern overlay
DotPatternOverlay(size: size)
.opacity(0.06)
// Content
VStack(spacing: size * 0.03) {
// Icon symbol
Image(systemName: config.iconSymbol)
.font(.system(size: iconSize, weight: .bold))
.foregroundStyle(
LinearGradient(
colors: [config.accentColor, config.accentColor.opacity(0.8)],
startPoint: .top,
endPoint: .bottom
)
)
.shadow(color: .black.opacity(0.3), radius: size * 0.02, y: size * 0.01)
// Subtitle
if let subtitle = config.subtitle {
Text(subtitle)
.font(.system(size: subtitleSize, weight: .black, design: .rounded))
.foregroundStyle(
LinearGradient(
colors: [config.accentColor, config.accentColor.opacity(0.7)],
startPoint: .top,
endPoint: .bottom
)
)
.shadow(color: .black.opacity(0.5), radius: size * 0.01)
}
// Title
Text(config.title)
.font(.system(size: titleSize, weight: .black, design: .rounded))
.tracking(size * 0.005)
.foregroundStyle(
LinearGradient(
colors: [.white, .white.opacity(0.85)],
startPoint: .top,
endPoint: .bottom
)
)
.shadow(color: .black.opacity(0.5), radius: size * 0.01)
}
}
.frame(width: size, height: size)
}
}
/// Dot pattern overlay for the icon background.
private struct DotPatternOverlay: View {
let size: CGFloat
private var spacing: CGFloat { size * 0.08 }
private var dotRadius: CGFloat { size * 0.012 }
var body: some View {
Canvas { context, canvasSize in
let rows = Int(canvasSize.height / spacing) + 1
let cols = Int(canvasSize.width / spacing) + 1
for row in 0..<rows {
for col in 0..<cols {
let offset: CGFloat = row % 2 == 0 ? 0 : spacing / 2
let x = CGFloat(col) * spacing + offset
let y = CGFloat(row) * spacing
let dot = Path(ellipseIn: CGRect(
x: x - dotRadius,
y: y - dotRadius,
width: dotRadius * 2,
height: dotRadius * 2
))
context.fill(dot, with: .color(.white))
}
}
}
}
}
// MARK: - Previews
#Preview("App Icon") {
AppIconView(config: .example, size: 512)
.clipShape(.rect(cornerRadius: 512 * 0.22))
.padding()
.background(Color.gray)
}
#Preview("Title Scaling") {
let shortTitle = AppIconConfig(title: "CAMERA", iconSymbol: "camera.fill")
let mediumTitle = AppIconConfig(title: "SETTINGS", iconSymbol: "gearshape.fill")
let longTitle = AppIconConfig(
title: "MESSENGER",
subtitle: "PRO",
iconSymbol: "message.fill",
primaryColor: Color(red: 0.20, green: 0.45, blue: 0.70),
secondaryColor: Color(red: 0.10, green: 0.25, blue: 0.45)
)
return HStack(spacing: 20) {
VStack {
AppIconView(config: shortTitle, size: 180)
.clipShape(.rect(cornerRadius: 180 * 0.22))
Text("6 chars").font(.caption)
}
VStack {
AppIconView(config: mediumTitle, size: 180)
.clipShape(.rect(cornerRadius: 180 * 0.22))
Text("8 chars").font(.caption)
}
VStack {
AppIconView(config: longTitle, size: 180)
.clipShape(.rect(cornerRadius: 180 * 0.22))
Text("9 chars").font(.caption)
}
}
.padding()
.background(Color.gray)
}
#Preview("All Sizes") {
LazyVGrid(columns: [GridItem(.adaptive(minimum: 100))], spacing: 20) {
ForEach([180, 120, 87, 60, 40], id: \.self) { size in
VStack {
AppIconView(config: .example, size: CGFloat(size))
.clipShape(.rect(cornerRadius: CGFloat(size) * 0.22))
Text("\(size)px").font(.caption)
}
}
}
.padding()
.background(Color.gray)
}

View File

@ -0,0 +1,64 @@
//
// AppLaunchView.swift
// Bedrock
//
// A wrapper view that shows an animated launch screen before transitioning
// to the main app content. Use this to create seamless animated launches.
//
import SwiftUI
/// A wrapper that shows an animated launch screen before the main content.
///
/// Usage:
/// ```swift
/// AppLaunchView(config: .baccarat) {
/// ContentView()
/// }
/// ```
public struct AppLaunchView<Content: View>: View {
let config: LaunchScreenConfig
let content: () -> Content
@State private var showLaunchScreen = true
/// Creates a launch wrapper.
/// - Parameters:
/// - config: The launch screen configuration.
/// - content: The main app content to show after the launch animation.
public init(
config: LaunchScreenConfig,
@ViewBuilder content: @escaping () -> Content
) {
self.config = config
self.content = content
}
public var body: some View {
ZStack {
// Main content (always rendered underneath)
content()
// Launch screen overlay
if showLaunchScreen {
LaunchScreenView(config: config)
.transition(.opacity)
.zIndex(1)
}
}
.task {
// Wait for launch animation to complete, then fade out
try? await Task.sleep(for: .seconds(2.0))
withAnimation(.easeOut(duration: 0.5)) {
showLaunchScreen = false
}
}
}
}
#Preview {
AppLaunchView(config: .example) {
Text("Main App Content")
.font(.largeTitle)
}
}

View File

@ -0,0 +1,569 @@
# Bedrock Branding Implementation Guide
A comprehensive guide to implementing the Bedrock branding system (app icon and launch screen) in your iOS app.
## Table of Contents
1. [Overview](#overview)
2. [What's Included](#whats-included)
3. [Step 1: Create BrandingConfig.swift](#step-1-create-brandingconfigswift)
4. [Step 2: Add Launch Screen to App Entry Point](#step-2-add-launch-screen-to-app-entry-point)
5. [Step 3: Set Launch Screen Background Color](#step-3-set-launch-screen-background-color)
6. [Step 4: Add Branding Tools to Settings (Optional)](#step-4-add-branding-tools-to-settings-optional)
7. [Step 5: Generate Your App Icon](#step-5-generate-your-app-icon)
8. [Step 6: Add Icon to Xcode Assets](#step-6-add-icon-to-xcode-assets)
9. [Configuration Reference](#configuration-reference)
10. [Complete Example](#complete-example)
11. [Troubleshooting](#troubleshooting)
---
## Overview
The Bedrock branding system provides a fully customizable app icon and launch screen that can be configured for any type of app. All visual elements are configurable through Swift code.
### Key Features
- **Customizable gradients**: Primary and secondary colors for backgrounds
- **Configurable icons**: Use any SF Symbols for your app identity
- **Multiple pattern styles**: Dots, grid, radial glow, or no pattern
- **Layout flexibility**: Icon above title, title above icon, icon only, or title only
- **Animated launch**: Smooth fade-in animations with configurable timing
- **Icon generator**: Built-in tool to export 1024×1024 PNG for App Store
---
## What's Included
The branding system consists of these files in `Bedrock/Sources/Bedrock/Branding/`:
| File | Purpose |
|------|---------|
| `AppIconView.swift` | Renders the app icon design |
| `LaunchScreenView.swift` | Animated launch screen view |
| `AppLaunchView.swift` | Wrapper that shows launch screen before main content |
| `IconGeneratorView.swift` | Development tool to export icon images |
| `IconRenderer.swift` | Utility to render views to images |
| `BrandingPreviewView.swift` | Preview tool for icons and launch screens |
---
## Step 1: Create BrandingConfig.swift
Create a new Swift file in your app's `Shared/` folder called `BrandingConfig.swift`. This file defines your app's branding.
### Template
```swift
//
// BrandingConfig.swift
// YourApp
//
// App-specific branding configurations for icons and launch screens.
//
import SwiftUI
import Bedrock
// MARK: - App Branding Colors
extension Color {
/// Your app's branding colors for icon and launch screen.
enum Branding {
/// Primary gradient color (top/leading).
static let primary = Color(red: 0.3, green: 0.5, blue: 0.8)
/// Secondary gradient color (bottom/trailing).
static let secondary = Color(red: 0.15, green: 0.3, blue: 0.5)
/// Accent color for icons and highlights.
static let accent = Color.white
}
}
// MARK: - App Icon Configuration
extension AppIconConfig {
/// Your app's icon configuration.
static let yourApp = AppIconConfig(
title: "YOUR APP",
subtitle: nil, // Optional: text below icon
iconSymbol: "star.fill", // SF Symbol name
primaryColor: Color.Branding.primary,
secondaryColor: Color.Branding.secondary,
accentColor: Color.Branding.accent
)
}
// MARK: - Launch Screen Configuration
extension LaunchScreenConfig {
/// Your app's launch screen configuration.
static let yourApp = LaunchScreenConfig(
title: "YOUR APP",
tagline: "Your tagline here",
iconSymbols: ["star.fill"],
primaryColor: Color.Branding.primary,
secondaryColor: Color.Branding.secondary,
accentColor: Color.Branding.accent
)
}
```
---
## Step 2: Add Launch Screen to App Entry Point
Update your `@main` App struct to wrap your content with `AppLaunchView`.
### Before
```swift
@main
struct YourApp: App {
var body: some Scene {
WindowGroup {
ContentView()
}
}
}
```
### After
```swift
import SwiftUI
import Bedrock
@main
struct YourApp: App {
var body: some Scene {
WindowGroup {
AppLaunchView(config: .yourApp) {
ContentView()
}
}
}
}
```
**What this does:**
- Shows an animated launch screen for ~2 seconds
- Fades smoothly into your main content
- Creates a polished, professional app opening experience
---
## Step 3: Set Launch Screen Background Color
To prevent a white flash before the SwiftUI launch screen appears, you need to set the system launch screen background color to match your branding.
### 3.1 Create the Color Asset
Create a folder in your asset catalog:
```
YourApp/Resources/Assets.xcassets/LaunchBackground.colorset/Contents.json
```
With this content (update RGB values to match your `Color.Branding.primary`):
```json
{
"colors" : [
{
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "0.800",
"green" : "0.500",
"red" : "0.300"
}
},
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
```
### 3.2 Update Xcode Project Settings
In your `project.pbxproj`, add this line after `INFOPLIST_KEY_UILaunchScreen_Generation = YES;` in **both** Debug and Release build configurations:
```
"INFOPLIST_KEY_UILaunchScreen_BackgroundColor" = LaunchBackground;
```
Or in Xcode:
1. Select your target → Build Settings
2. Search for "Launch Screen"
3. Set "Asset Catalog Launch Image Set Name" or add User-Defined setting
**Important:** After making this change:
1. Clean build (Cmd+Shift+K)
2. Delete app from simulator/device
3. Build and run again
---
## Step 4: Add Branding Tools to Settings (Optional)
Add debug tools to your settings view for generating and previewing icons during development.
### Add to SettingsView
```swift
import SwiftUI
import Bedrock
struct SettingsView: View {
var body: some View {
NavigationStack {
List {
// ... your normal settings ...
#if DEBUG
Section("Debug") {
NavigationLink("Icon Generator") {
IconGeneratorView(config: .yourApp, appName: "YourApp")
}
NavigationLink("Branding Preview") {
BrandingPreviewView(
iconConfig: .yourApp,
launchConfig: .yourApp,
appName: "YourApp"
)
}
}
#endif
}
}
}
}
```
**Important:** Wrap in `#if DEBUG` so these tools are excluded from App Store builds.
---
## Step 5: Generate Your App Icon
### Using IconGeneratorView (Recommended)
1. **Build and run your app** in DEBUG mode
2. **Open Settings** → Debug section
3. **Tap "Icon Generator"**
4. **Tap "Generate & Save Icon"**
5. **Wait for confirmation**: "✅ Icon saved to Documents folder!"
### Retrieve the Icon
**On Simulator:**
1. Open Finder
2. Go to: `~/Library/Developer/CoreSimulator/Devices/`
3. Find your simulator device folder (sorted by date)
4. Navigate to: `data/Containers/Data/Application/[YourApp-UUID]/Documents/`
5. Copy `AppIcon.png`
**On Physical Device:**
1. Open **Files** app on your device
2. Navigate to: **On My iPhone** → **YourApp**
3. Find `AppIcon.png`
4. **AirDrop** or **share** to your Mac
**Alternative (Xcode):**
1. Go to **Window** → **Devices and Simulators**
2. Select your device/simulator
3. Find your app → Click **⚙️ gear** → **Download Container**
4. Right-click downloaded file → **Show Package Contents**
5. Navigate to `AppData/Documents/` and copy `AppIcon.png`
---
## Step 6: Add Icon to Xcode Assets
1. **Open your Xcode project**
2. **Navigate to** `Assets.xcassets``AppIcon`
3. **Drag `AppIcon.png`** into the **1024×1024** slot
4. Xcode automatically generates all required sizes
**Verify:**
1. Clean build and run
2. Check home screen for new icon
3. If unchanged, delete app and reinstall
---
## Configuration Reference
### AppIconConfig
| Property | Type | Default | Description |
|----------|------|---------|-------------|
| `title` | `String` | Required | App name (uppercase recommended) |
| `subtitle` | `String?` | `nil` | Optional text below icon |
| `iconSymbol` | `String` | Required | SF Symbol name |
| `primaryColor` | `Color` | Blue | Top-left gradient color |
| `secondaryColor` | `Color` | Dark blue | Bottom-right gradient color |
| `accentColor` | `Color` | Light blue | Icon and text highlight color |
### LaunchScreenConfig
| Property | Type | Default | Description |
|----------|------|---------|-------------|
| `title` | `String` | Required | App name displayed on launch |
| `subtitle` | `String?` | `nil` | Large text (like "PRO" or version) |
| `tagline` | `String?` | `nil` | Small text at bottom of screen |
| `iconSymbols` | `[String]` | `["star.fill"]` | Array of SF Symbol names |
| `cornerSymbol` | `String?` | `nil` | Symbol for corner decorations (nil = none) |
| `decorativeSymbol` | `String?` | `"circle.fill"` | Symbol in decorative line (nil = hide line) |
| `patternStyle` | `LaunchPatternStyle` | `.dots` | Background pattern style |
| `layoutStyle` | `LaunchLayoutStyle` | `.iconAboveTitle` | Content layout arrangement |
| `primaryColor` | `Color` | Blue-gray | Top gradient color |
| `secondaryColor` | `Color` | Dark blue | Bottom gradient color |
| `accentColor` | `Color` | Light blue | Icons and highlights |
| `titleColor` | `Color` | `.white` | Title text color |
| `iconSize` | `CGFloat` | `48` | Size of icon symbols |
| `titleSize` | `CGFloat` | `42` | Size of title text |
| `subtitleSize` | `CGFloat` | `72` | Size of subtitle text |
| `iconSpacing` | `CGFloat` | `8` | Spacing between icons |
| `animationDuration` | `Double` | `0.6` | Fade-in animation duration |
| `showLoadingIndicator` | `Bool` | `false` | Show spinner at bottom |
### LaunchPatternStyle
| Value | Description |
|-------|-------------|
| `.none` | Clean background, no pattern |
| `.dots` | Subtle dot pattern (default) |
| `.grid` | Grid lines pattern |
| `.radial` | Radial gradient glow from center |
### LaunchLayoutStyle
| Value | Description |
|-------|-------------|
| `.iconAboveTitle` | Icons at top, title below (default) |
| `.titleAboveIcon` | Title at top, icons below |
| `.iconOnly` | Only show icons, no text |
| `.titleOnly` | Only show text, no icons |
---
## Complete Example
Here's a full example for a camera app:
### File: `YourApp/Shared/BrandingConfig.swift`
```swift
import SwiftUI
import Bedrock
// MARK: - App Branding Colors
extension Color {
enum Branding {
// Vibrant magenta/rose gradient
static let primary = Color(red: 0.85, green: 0.25, blue: 0.45)
static let secondary = Color(red: 0.45, green: 0.12, blue: 0.35)
static let accent = Color.white
}
}
// MARK: - App Icon Configuration
extension AppIconConfig {
static let myCamera = AppIconConfig(
title: "CAMERA",
subtitle: "PRO",
iconSymbol: "camera.fill",
primaryColor: Color.Branding.primary,
secondaryColor: Color.Branding.secondary,
accentColor: Color.Branding.accent
)
}
// MARK: - Launch Screen Configuration
extension LaunchScreenConfig {
static let myCamera = LaunchScreenConfig(
title: "CAMERA PRO",
tagline: "Capture the Moment",
iconSymbols: ["camera.fill", "sparkles"],
cornerSymbol: "sparkle", // Sparkles in corners
decorativeSymbol: "circle.fill", // Circle in decorative line
patternStyle: .radial, // Radial glow effect
layoutStyle: .iconAboveTitle,
primaryColor: Color.Branding.primary,
secondaryColor: Color.Branding.secondary,
accentColor: Color.Branding.accent,
titleColor: .white,
iconSize: 52,
titleSize: 38,
iconSpacing: 12,
animationDuration: 0.6
)
}
```
### File: `YourApp/App/MyCameraApp.swift`
```swift
import SwiftUI
import Bedrock
@main
struct MyCameraApp: App {
var body: some Scene {
WindowGroup {
AppLaunchView(config: .myCamera) {
ContentView()
}
}
}
}
```
---
## Troubleshooting
### White flash before launch screen
**Cause:** iOS system launch screen doesn't match your branding colors.
**Solution:** Follow [Step 3](#step-3-set-launch-screen-background-color) to add `LaunchBackground` color to your asset catalog and configure the project build settings.
### Can't find types like `AppIconConfig`
**Solution:** Make sure you have `import Bedrock` at the top of your file.
### Launch screen doesn't appear
**Solution:**
- Verify `AppLaunchView` wraps your content in the App struct
- Check that you're using the correct config name (e.g., `.myCamera`)
- Ensure the config extension is defined in `BrandingConfig.swift`
### Icon looks different than preview
**Explanation:** iOS applies a superellipse mask to all app icons.
**Solution:** Don't add your own rounded corners—iOS does this automatically.
### "Icon saved" but can't find file
**Solution:**
1. Open **Files** app on device/simulator
2. Navigate to: **On My iPhone/iPad****[Your App Name]**
3. If folder doesn't exist, the app may need Files access
**Alternative:** Use Xcode's Devices and Simulators window to download the app container.
### Icon doesn't update in simulator
**Solution:**
1. Clean build folder: Product → Clean Build Folder (Cmd+Shift+K)
2. Delete app from simulator
3. Rebuild and run
### DEBUG section not showing in settings
**Solution:**
- Ensure you're running a DEBUG build (not Release)
- Check that code is wrapped in `#if DEBUG ... #endif`
- Verify your settings view is inside a `NavigationStack`
---
## Color Palette Ideas
### Professional Blue
```swift
primaryColor: Color(red: 0.15, green: 0.30, blue: 0.55)
secondaryColor: Color(red: 0.08, green: 0.15, blue: 0.30)
accentColor: .white
```
### Vibrant Pink/Magenta
```swift
primaryColor: Color(red: 0.85, green: 0.25, blue: 0.45)
secondaryColor: Color(red: 0.45, green: 0.12, blue: 0.35)
accentColor: .white
```
### Nature Green
```swift
primaryColor: Color(red: 0.20, green: 0.55, blue: 0.35)
secondaryColor: Color(red: 0.10, green: 0.30, blue: 0.20)
accentColor: Color(red: 0.85, green: 0.95, blue: 0.85)
```
### Warm Orange/Gold
```swift
primaryColor: Color(red: 0.95, green: 0.60, blue: 0.20)
secondaryColor: Color(red: 0.70, green: 0.35, blue: 0.10)
accentColor: .white
```
### Dark/Minimal
```swift
primaryColor: Color(red: 0.12, green: 0.12, blue: 0.15)
secondaryColor: .black
accentColor: .white
patternStyle: .none
```
---
## SF Symbol Recommendations
### Photography/Camera
- `camera.fill`, `camera.circle.fill`
- `photo.fill`, `photo.stack.fill`
- `sparkles`, `wand.and.stars`
### Social/Communication
- `message.fill`, `bubble.left.fill`
- `person.fill`, `person.2.fill`
- `heart.fill`, `star.fill`
### Productivity
- `doc.fill`, `folder.fill`
- `checkmark.circle.fill`
- `calendar`, `clock.fill`
### Music/Media
- `music.note`, `waveform`
- `play.fill`, `headphones`
- `mic.fill`, `speaker.wave.2.fill`
### Utility
- `gearshape.fill`, `wrench.fill`
- `magnifyingglass`, `location.fill`
- `bolt.fill`, `battery.100`
---
## Summary Checklist
- [ ] Create `BrandingConfig.swift` with your app's configurations
- [ ] Add `AppLaunchView` wrapper to your App entry point
- [ ] Create `LaunchBackground.colorset` in asset catalog matching primary color
- [ ] Add `INFOPLIST_KEY_UILaunchScreen_BackgroundColor` to project settings
- [ ] (Optional) Add debug section to settings with `IconGeneratorView` and `BrandingPreviewView`
- [ ] Build and run in DEBUG mode
- [ ] Generate icon using Icon Generator tool
- [ ] Retrieve icon PNG from device/simulator
- [ ] Add 1024×1024 PNG to `Assets.xcassets/AppIcon`
- [ ] Clean build and reinstall to verify icon and launch screen
---
**Happy Branding! 🎨✨**

View File

@ -0,0 +1,91 @@
//
// BrandingPreviewView.swift
// Bedrock
//
// Development view for previewing and exporting app icons and launch screens.
// Access this during development to generate icon assets.
//
import SwiftUI
/// Preview view for app branding assets.
/// Use this during development to preview icons and launch screens.
public struct BrandingPreviewView: View {
let iconConfig: AppIconConfig
let launchConfig: LaunchScreenConfig
let appName: String
// Development view: fixed sizes acceptable
private let largePreviewSize: CGFloat = 300
private let iconCornerRadiusRatio: CGFloat = 0.22
/// Creates a branding preview view.
/// - Parameters:
/// - iconConfig: The app icon configuration for this app.
/// - launchConfig: The launch screen configuration for this app.
/// - appName: The app name for display purposes.
public init(
iconConfig: AppIconConfig,
launchConfig: LaunchScreenConfig,
appName: String
) {
self.iconConfig = iconConfig
self.launchConfig = launchConfig
self.appName = appName
}
public var body: some View {
TabView {
// App Icon Preview
ScrollView {
VStack(spacing: 32) {
Text("App Icon")
.font(.largeTitle.bold())
AppIconView(config: iconConfig, size: largePreviewSize)
.clipShape(.rect(cornerRadius: largePreviewSize * iconCornerRadiusRatio))
.shadow(radius: 20)
Text("1024 × 1024px")
.font(.caption)
.foregroundStyle(.secondary)
instructionsSection
}
.padding()
}
.tabItem {
Label("Icon", systemImage: "app.fill")
}
// Launch Screen Preview
LaunchScreenView(config: launchConfig)
.tabItem {
Label("Launch", systemImage: "rectangle.portrait.fill")
}
}
}
private var instructionsSection: some View {
VStack(alignment: .leading, spacing: 12) {
Text("To Export")
.font(.headline)
Text("Use the Icon Generator in Settings → DEBUG to save the 1024px icon to the Files app, then add it to Xcode's Assets.xcassets/AppIcon.")
.font(.callout)
}
.foregroundStyle(.secondary)
.frame(maxWidth: .infinity, alignment: .leading)
.padding()
.background(Color.gray.opacity(0.1))
.clipShape(.rect(cornerRadius: 12))
}
}
#Preview {
BrandingPreviewView(
iconConfig: .example,
launchConfig: .example,
appName: "MyApp"
)
}

View File

@ -0,0 +1,187 @@
//
// IconGeneratorView.swift
// Bedrock
//
// Development tool to generate and export app icon images.
// Run this view, tap the button, then find the icons in the Files app.
//
import SwiftUI
/// A development view that generates and saves app icon images.
/// After running, find the icons in Files app On My iPhone [App Name]
public struct IconGeneratorView: View {
let config: AppIconConfig
let appName: String
@State private var status: String = "Tap the button to generate the icon"
@State private var isGenerating = false
@State private var generatedIcon: GeneratedIconInfo?
// Development view: fixed sizes acceptable
private let previewSize: CGFloat = 200
private let iconCornerRadiusRatio: CGFloat = 0.22
/// Creates a new icon generator view.
/// - Parameters:
/// - config: The app icon configuration to use for rendering.
/// - appName: The app name for display in instructions (e.g., "SelfieCam", "MyApp").
public init(config: AppIconConfig, appName: String) {
self.config = config
self.appName = appName
}
public var body: some View {
NavigationStack {
ScrollView {
VStack(spacing: 24) {
// Preview
AppIconView(config: config, size: previewSize)
.clipShape(.rect(cornerRadius: previewSize * iconCornerRadiusRatio))
.shadow(radius: 10)
Text("App Icon Preview")
.font(.headline)
// Generate button
Button {
Task {
await generateIcon()
}
} label: {
HStack {
if isGenerating {
ProgressView()
.tint(.white)
}
Text(isGenerating ? "Generating..." : "Generate & Save Icon")
}
.font(.headline)
.foregroundStyle(.white)
.frame(maxWidth: .infinity)
.padding()
.background(isGenerating ? Color.gray : Color.blue)
.clipShape(.rect(cornerRadius: 12))
}
.disabled(isGenerating)
.padding(.horizontal)
// Status
Text(status)
.font(.callout)
.foregroundStyle(.secondary)
.multilineTextAlignment(.center)
.padding(.horizontal)
// Generated icon confirmation
if let icon = generatedIcon {
HStack {
Image(systemName: "checkmark.circle.fill")
.foregroundStyle(.green)
Text(icon.filename)
.font(.callout.monospaced())
Spacer()
Text("\(Int(icon.size))px")
.font(.callout)
.foregroundStyle(.secondary)
}
.padding()
.background(Color.green.opacity(0.1))
.clipShape(.rect(cornerRadius: 12))
.padding(.horizontal)
}
// Instructions
instructionsSection
}
.padding(.vertical)
}
.navigationTitle("Icon Generator")
}
}
private var instructionsSection: some View {
VStack(alignment: .leading, spacing: 12) {
Text("After generating:")
.font(.headline)
VStack(alignment: .leading, spacing: 8) {
instructionRow(number: 1, text: "Open Files app on your device/simulator")
instructionRow(number: 2, text: "Navigate to: On My iPhone → \(appName)")
instructionRow(number: 3, text: "Find AppIcon.png (1024×1024)")
instructionRow(number: 4, text: "AirDrop or share to your Mac")
instructionRow(number: 5, text: "Drag into Xcode's Assets.xcassets/AppIcon")
}
Divider()
Text("Note: iOS uses a single 1024px icon")
.font(.subheadline.bold())
Text("Xcode automatically generates all required sizes from the 1024px source.")
.font(.caption)
.foregroundStyle(.secondary)
}
.padding()
.background(Color.gray.opacity(0.1))
.clipShape(.rect(cornerRadius: 12))
.padding(.horizontal)
}
private func instructionRow(number: Int, text: String) -> some View {
HStack(alignment: .top, spacing: 8) {
Text("\(number).")
.font(.callout.bold())
.foregroundStyle(.blue)
Text(text)
.font(.callout)
}
}
@MainActor
private func generateIcon() async {
isGenerating = true
generatedIcon = nil
status = "Generating icon..."
let documentsPath = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]
// Render the 1024px icon (the only size needed for modern iOS)
let view = AppIconView(config: config, size: 1024)
let renderer = ImageRenderer(content: view)
renderer.scale = 1.0
if let uiImage = renderer.uiImage,
let data = uiImage.pngData() {
let filename = "AppIcon.png"
let fileURL = documentsPath.appending(path: filename)
do {
try data.write(to: fileURL)
generatedIcon = GeneratedIconInfo(filename: filename, size: 1024)
status = "✅ Icon saved to Documents folder!\nOpen Files app to find it."
} catch {
status = "Error saving icon: \(error.localizedDescription)"
}
} else {
status = "⚠️ Failed to render icon"
}
isGenerating = false
}
}
/// Information about a generated icon file.
public struct GeneratedIconInfo: Identifiable, Sendable {
public let id = UUID()
public let filename: String
public let size: CGFloat
public init(filename: String, size: CGFloat) {
self.filename = filename
self.size = size
}
}
#Preview {
IconGeneratorView(config: .example, appName: "MyApp")
}

View File

@ -0,0 +1,139 @@
//
// IconRenderer.swift
// Bedrock
//
// Utility to render SwiftUI views to images for app icons.
//
import SwiftUI
/// Utility to render SwiftUI views to images.
@MainActor
public struct IconRenderer {
/// Standard iOS app icon sizes.
public static let iOSIconSizes: [CGFloat] = [
1024, // App Store
180, // iPhone @3x
120, // iPhone @2x
167, // iPad Pro @2x
152, // iPad @2x
76, // iPad @1x
40, // Spotlight @2x
60, // Spotlight @3x
29, // Settings @1x
58, // Settings @2x
87 // Settings @3x
]
/// Renders an app icon view to a UIImage.
/// - Parameters:
/// - config: The app icon configuration.
/// - size: The size to render at (default 1024 for App Store).
/// - Returns: A rendered UIImage.
public static func renderAppIcon(config: AppIconConfig, size: CGFloat = 1024) -> UIImage? {
let view = AppIconView(config: config, size: size)
let renderer = ImageRenderer(content: view)
renderer.scale = 1.0
return renderer.uiImage
}
/// Renders app icons at all standard iOS sizes.
/// - Parameter config: The app icon configuration.
/// - Returns: Dictionary of size to UIImage.
public static func renderAllSizes(config: AppIconConfig) -> [CGFloat: UIImage] {
var images: [CGFloat: UIImage] = [:]
for size in iOSIconSizes {
if let image = renderAppIcon(config: config, size: size) {
images[size] = image
}
}
return images
}
/// Renders a launch screen to a UIImage.
/// - Parameters:
/// - config: The launch screen configuration.
/// - size: The size to render at.
/// - Returns: A rendered UIImage.
public static func renderLaunchScreen(config: LaunchScreenConfig, size: CGSize) -> UIImage? {
let view = StaticLaunchScreenView(config: config)
.frame(width: size.width, height: size.height)
let renderer = ImageRenderer(content: view)
renderer.scale = 1.0
return renderer.uiImage
}
}
// MARK: - Icon Export View
/// A development view for previewing and exporting app icons.
/// Add this to your app during development to easily export icons.
public struct IconExportView: View {
let config: AppIconConfig
@State private var exportedMessage: String?
public init(config: AppIconConfig) {
self.config = config
}
public var body: some View {
ScrollView {
VStack(spacing: 24) {
Text("App Icon Preview")
.font(.title.bold())
// Large preview
AppIconView(config: config, size: 256)
.clipShape(.rect(cornerRadius: 256 * 0.22))
.shadow(radius: 10)
// Size variants
Text("Size Variants")
.font(.headline)
LazyVGrid(columns: [
GridItem(.adaptive(minimum: 100))
], spacing: 16) {
ForEach([180, 120, 87, 60, 40], id: \.self) { size in
VStack {
AppIconView(config: config, size: CGFloat(size))
.clipShape(.rect(cornerRadius: CGFloat(size) * 0.22))
Text("\(size)pt")
.font(.caption)
.foregroundStyle(.secondary)
}
}
}
// Export instructions
Text("Export Instructions")
.font(.headline)
.padding(.top)
VStack(alignment: .leading, spacing: 8) {
Text("1. Use Xcode's preview to screenshot these icons")
Text("2. Or use IconRenderer.renderAppIcon() in code")
Text("3. Add generated images to Assets.xcassets/AppIcon")
}
.font(.callout)
.foregroundStyle(.secondary)
.frame(maxWidth: .infinity, alignment: .leading)
.padding()
.background(Color.gray.opacity(0.1))
.clipShape(.rect(cornerRadius: 12))
if let message = exportedMessage {
Text(message)
.foregroundStyle(.green)
}
}
.padding()
}
}
}
#Preview("Icon Export") {
IconExportView(config: .example)
}

View File

@ -0,0 +1,523 @@
//
// LaunchScreenView.swift
// Bedrock
//
// A reusable launch screen design that can be customized for any app.
//
import SwiftUI
// MARK: - Pattern Style
/// The background pattern style for launch screens.
public enum LaunchPatternStyle: Sendable {
/// No pattern overlay.
case none
/// Subtle dot pattern.
case dots
/// Grid lines pattern.
case grid
/// Radial gradient overlay for depth.
case radial
}
// MARK: - Layout Style
/// The layout style for the launch screen content.
public enum LaunchLayoutStyle: Sendable {
/// Icon above title (default).
case iconAboveTitle
/// Title above icon.
case titleAboveIcon
/// Icon only, no title.
case iconOnly
/// Title only, no icon.
case titleOnly
}
// MARK: - Configuration
/// Configuration for the launch screen appearance.
public struct LaunchScreenConfig: Sendable {
// MARK: Content
public let title: String
public let subtitle: String?
public let tagline: String?
public let iconSymbols: [String]
// MARK: Decorations
public let cornerSymbol: String?
public let decorativeSymbol: String?
public let patternStyle: LaunchPatternStyle
public let layoutStyle: LaunchLayoutStyle
// MARK: Colors
public let primaryColor: Color
public let secondaryColor: Color
public let accentColor: Color
public let titleColor: Color
// MARK: Sizing
public let iconSize: CGFloat
public let titleSize: CGFloat
public let subtitleSize: CGFloat
public let iconSpacing: CGFloat
// MARK: Animation
public let animationDuration: Double
public let showLoadingIndicator: Bool
public init(
title: String,
subtitle: String? = nil,
tagline: String? = nil,
iconSymbols: [String] = ["star.fill"],
cornerSymbol: String? = nil,
decorativeSymbol: String? = "circle.fill",
patternStyle: LaunchPatternStyle = .dots,
layoutStyle: LaunchLayoutStyle = .iconAboveTitle,
primaryColor: Color = Color(red: 0.15, green: 0.20, blue: 0.30),
secondaryColor: Color = Color(red: 0.08, green: 0.10, blue: 0.18),
accentColor: Color = Color(red: 0.4, green: 0.7, blue: 1.0),
titleColor: Color = .white,
iconSize: CGFloat = 48,
titleSize: CGFloat = 42,
subtitleSize: CGFloat = 72,
iconSpacing: CGFloat = 8,
animationDuration: Double = 0.6,
showLoadingIndicator: Bool = false
) {
self.title = title
self.subtitle = subtitle
self.tagline = tagline
self.iconSymbols = iconSymbols
self.cornerSymbol = cornerSymbol
self.decorativeSymbol = decorativeSymbol
self.patternStyle = patternStyle
self.layoutStyle = layoutStyle
self.primaryColor = primaryColor
self.secondaryColor = secondaryColor
self.accentColor = accentColor
self.titleColor = titleColor
self.iconSize = iconSize
self.titleSize = titleSize
self.subtitleSize = subtitleSize
self.iconSpacing = iconSpacing
self.animationDuration = animationDuration
self.showLoadingIndicator = showLoadingIndicator
}
// MARK: - Example Configuration (for previews only)
/// Example configuration for Bedrock previews.
/// Apps should define their own configs in `BrandingConfig.swift`.
public static let example = LaunchScreenConfig(
title: "MY APP",
tagline: "Your App Tagline",
iconSymbols: ["star.fill", "sparkles"]
)
}
// MARK: - Launch Screen View
/// A customizable launch screen view for any app.
public struct LaunchScreenView: View {
let config: LaunchScreenConfig
@State private var logoScale: CGFloat = 0.8
@State private var logoOpacity: Double = 0
@State private var taglineOffset: CGFloat = 20
@State private var taglineOpacity: Double = 0
public init(config: LaunchScreenConfig) {
self.config = config
}
public var body: some View {
GeometryReader { geometry in
ZStack {
// Background gradient
backgroundGradient
// Pattern overlay
patternOverlay
// Decorative corner elements (optional)
if config.cornerSymbol != nil {
cornerDecorations(in: geometry)
}
// Main content
VStack(spacing: 0) {
Spacer()
// Logo section
logoSection
.scaleEffect(logoScale)
.opacity(logoOpacity)
Spacer()
// Bottom tagline
if let tagline = config.tagline {
Text(tagline)
.font(.system(size: 14, weight: .medium, design: .rounded))
.foregroundStyle(config.titleColor.opacity(0.6))
.tracking(2)
.padding(.bottom, 40)
.offset(y: taglineOffset)
.opacity(taglineOpacity)
}
// Loading indicator
if config.showLoadingIndicator {
ProgressView()
.progressViewStyle(.circular)
.tint(config.accentColor)
.scaleEffect(1.2)
.padding(.bottom, 60)
}
}
}
}
.ignoresSafeArea()
.onAppear {
withAnimation(.easeOut(duration: config.animationDuration)) {
logoScale = 1.0
logoOpacity = 1.0
}
withAnimation(.easeOut(duration: config.animationDuration).delay(config.animationDuration * 0.5)) {
taglineOffset = 0
taglineOpacity = 1.0
}
}
}
// MARK: - Background
private var backgroundGradient: some View {
LinearGradient(
colors: [config.primaryColor, config.secondaryColor],
startPoint: .top,
endPoint: .bottom
)
}
@ViewBuilder
private var patternOverlay: some View {
switch config.patternStyle {
case .none:
EmptyView()
case .dots:
dotsPattern.opacity(0.03)
case .grid:
gridPattern.opacity(0.05)
case .radial:
radialOverlay.opacity(0.15)
}
}
private var dotsPattern: some View {
Canvas { context, size in
let spacing: CGFloat = 50
let dotRadius: CGFloat = 3
let rows = Int(size.height / spacing) + 1
let cols = Int(size.width / spacing) + 1
for row in 0..<rows {
for col in 0..<cols {
let offset: CGFloat = row % 2 == 0 ? 0 : spacing / 2
let x = CGFloat(col) * spacing + offset
let y = CGFloat(row) * spacing
let dot = Path(ellipseIn: CGRect(
x: x - dotRadius,
y: y - dotRadius,
width: dotRadius * 2,
height: dotRadius * 2
))
context.fill(dot, with: .color(.white))
}
}
}
}
private var gridPattern: some View {
Canvas { context, size in
let spacing: CGFloat = 40
let lineWidth: CGFloat = 0.5
// Vertical lines
var x: CGFloat = 0
while x < size.width {
let line = Path { path in
path.move(to: CGPoint(x: x, y: 0))
path.addLine(to: CGPoint(x: x, y: size.height))
}
context.stroke(line, with: .color(.white), lineWidth: lineWidth)
x += spacing
}
// Horizontal lines
var y: CGFloat = 0
while y < size.height {
let line = Path { path in
path.move(to: CGPoint(x: 0, y: y))
path.addLine(to: CGPoint(x: size.width, y: y))
}
context.stroke(line, with: .color(.white), lineWidth: lineWidth)
y += spacing
}
}
}
private var radialOverlay: some View {
RadialGradient(
colors: [config.accentColor.opacity(0.3), .clear],
center: .center,
startRadius: 0,
endRadius: 300
)
}
// MARK: - Corner Decorations
private func cornerDecorations(in geometry: GeometryProxy) -> some View {
ZStack {
// Top-left
cornerSymbolView
.position(x: 50, y: 80)
// Top-right
cornerSymbolView
.rotationEffect(.degrees(90))
.position(x: geometry.size.width - 50, y: 80)
// Bottom-left
cornerSymbolView
.rotationEffect(.degrees(-90))
.position(x: 50, y: geometry.size.height - 80)
// Bottom-right
cornerSymbolView
.rotationEffect(.degrees(180))
.position(x: geometry.size.width - 50, y: geometry.size.height - 80)
}
.opacity(0.15)
}
private var cornerSymbolView: some View {
Group {
if let symbol = config.cornerSymbol {
Image(systemName: symbol)
.font(.system(size: 30))
.foregroundStyle(config.accentColor)
}
}
}
// MARK: - Logo Section
@ViewBuilder
private var logoSection: some View {
switch config.layoutStyle {
case .iconAboveTitle:
VStack(spacing: 16) {
iconRow
subtitleView
titleView
decorativeLineView
}
case .titleAboveIcon:
VStack(spacing: 16) {
titleView
subtitleView
iconRow
decorativeLineView
}
case .iconOnly:
VStack(spacing: 16) {
iconRow
decorativeLineView
}
case .titleOnly:
VStack(spacing: 16) {
subtitleView
titleView
decorativeLineView
}
}
}
private var iconRow: some View {
HStack(spacing: config.iconSpacing) {
ForEach(config.iconSymbols.indices, id: \.self) { index in
Image(systemName: config.iconSymbols[index])
.font(.system(size: config.iconSize, weight: .bold))
.foregroundStyle(config.accentColor)
.shadow(color: .black.opacity(0.3), radius: 4, y: 2)
}
}
}
@ViewBuilder
private var subtitleView: some View {
if let subtitle = config.subtitle {
Text(subtitle)
.font(.system(size: config.subtitleSize, weight: .black, design: .rounded))
.foregroundStyle(
LinearGradient(
colors: [config.accentColor, config.accentColor.opacity(0.7)],
startPoint: .top,
endPoint: .bottom
)
)
.shadow(color: .black.opacity(0.4), radius: 4, y: 2)
}
}
private var titleView: some View {
Text(config.title)
.font(.system(size: config.titleSize, weight: .black, design: .rounded))
.tracking(6)
.foregroundStyle(
LinearGradient(
colors: [config.titleColor, config.titleColor.opacity(0.85)],
startPoint: .top,
endPoint: .bottom
)
)
.shadow(color: .black.opacity(0.4), radius: 4, y: 2)
}
@ViewBuilder
private var decorativeLineView: some View {
if let symbol = config.decorativeSymbol {
HStack(spacing: 12) {
decorativeLine
Image(systemName: symbol)
.font(.system(size: 10))
.foregroundStyle(config.accentColor.opacity(0.6))
decorativeLine
}
.frame(width: 200)
}
}
private var decorativeLine: some View {
Rectangle()
.fill(
LinearGradient(
colors: [.clear, config.accentColor.opacity(0.4), .clear],
startPoint: .leading,
endPoint: .trailing
)
)
.frame(height: 1)
}
}
// MARK: - Static Launch Screen (for LaunchScreen.storyboard alternative)
/// A static version of the launch screen without animations.
/// Use this if you need to render a static image.
public struct StaticLaunchScreenView: View {
let config: LaunchScreenConfig
public init(config: LaunchScreenConfig) {
self.config = config
}
public var body: some View {
GeometryReader { geometry in
ZStack {
// Background
LinearGradient(
colors: [config.primaryColor, config.secondaryColor],
startPoint: .top,
endPoint: .bottom
)
// Logo
VStack(spacing: 16) {
if config.layoutStyle != .titleOnly {
HStack(spacing: config.iconSpacing) {
ForEach(config.iconSymbols.indices, id: \.self) { index in
Image(systemName: config.iconSymbols[index])
.font(.system(size: config.iconSize, weight: .bold))
.foregroundStyle(config.accentColor)
}
}
}
if let subtitle = config.subtitle, config.layoutStyle != .iconOnly {
Text(subtitle)
.font(.system(size: config.subtitleSize, weight: .black, design: .rounded))
.foregroundStyle(config.accentColor)
}
if config.layoutStyle != .iconOnly {
Text(config.title)
.font(.system(size: config.titleSize, weight: .black, design: .rounded))
.tracking(6)
.foregroundStyle(config.titleColor)
}
}
}
}
.ignoresSafeArea()
}
}
// MARK: - Previews
#Preview("Launch Screen - Default") {
LaunchScreenView(config: .example)
}
#Preview("Launch Screen - Minimal") {
let config = LaunchScreenConfig(
title: "CAMERA",
iconSymbols: ["camera.fill"],
patternStyle: .none,
primaryColor: Color(red: 0.1, green: 0.1, blue: 0.15),
secondaryColor: .black,
accentColor: .white
)
return LaunchScreenView(config: config)
}
#Preview("Launch Screen - Radial") {
let config = LaunchScreenConfig(
title: "SELFIE CAM",
tagline: "Look Your Best",
iconSymbols: ["camera.fill", "sparkles"],
cornerSymbol: "sparkle",
patternStyle: .radial,
primaryColor: Color(red: 0.85, green: 0.25, blue: 0.45),
secondaryColor: Color(red: 0.45, green: 0.12, blue: 0.35),
accentColor: .white
)
return LaunchScreenView(config: config)
}
#Preview("Launch Screen - Grid") {
let config = LaunchScreenConfig(
title: "NOTES",
iconSymbols: ["note.text"],
patternStyle: .grid,
layoutStyle: .iconAboveTitle,
primaryColor: Color(red: 0.95, green: 0.85, blue: 0.5),
secondaryColor: Color(red: 0.9, green: 0.75, blue: 0.3),
accentColor: Color(red: 0.3, green: 0.2, blue: 0.1),
titleColor: Color(red: 0.3, green: 0.2, blue: 0.1)
)
return LaunchScreenView(config: config)
}
#Preview("Static Launch") {
StaticLaunchScreenView(config: .example)
}

View File

@ -1,9 +1,93 @@
{
"sourceLanguage" : "en",
"strings" : {
"%lld." : {
"comment" : "A numbered list item with a callout number and accompanying text. The first argument is the number of the item. The second argument is the text describing the item.",
"isCommentAutoGenerated" : true
},
"%lld%%" : {
"comment" : "A text label showing the current volume percentage. The argument is the volume as a percentage (0.0 to 1.0).",
"isCommentAutoGenerated" : true
},
"%lldpt" : {
"comment" : "A caption below an app icon that shows its size in points. The argument is the size of the icon in points.",
"isCommentAutoGenerated" : true
},
"%lldpx" : {
"comment" : "A label displaying the size of the generated app icon. The argument is the size of the icon in pixels.",
"isCommentAutoGenerated" : true
},
"1. Use Xcode's preview to screenshot these icons" : {
"comment" : "An instruction in the icon export view.",
"isCommentAutoGenerated" : true
},
"2. Or use IconRenderer.renderAppIcon() in code" : {
"comment" : "An instruction within the icon export view, explaining how to generate icons using code.",
"isCommentAutoGenerated" : true
},
"3. Add generated images to Assets.xcassets/AppIcon" : {
"comment" : "Instructions for adding generated app icon images to Xcode's asset catalog.",
"isCommentAutoGenerated" : true
},
"1024 × 1024px" : {
"comment" : "A description of the size of the app icon.",
"isCommentAutoGenerated" : true
},
"After generating:" : {
"comment" : "A heading for the instructions section of the IconGeneratorView.",
"isCommentAutoGenerated" : true
},
"App Icon" : {
"comment" : "A heading for the app icon preview section.",
"isCommentAutoGenerated" : true
},
"App Icon Preview" : {
"comment" : "A heading describing the preview of the app icon.",
"isCommentAutoGenerated" : true
},
"Export Instructions" : {
"comment" : "A section header describing how to export app icons.",
"isCommentAutoGenerated" : true
},
"Generate & Save Icon" : {
"comment" : "A button label that triggers icon generation and saving.",
"isCommentAutoGenerated" : true
},
"Generating..." : {
"comment" : "A label indicating that an icon is being generated.",
"isCommentAutoGenerated" : true
},
"Icon" : {
"comment" : "A tab label for the \"Icon\" tab in the branding preview view.",
"isCommentAutoGenerated" : true
},
"Icon Generator" : {
"comment" : "The title of the icon generator view.",
"isCommentAutoGenerated" : true
},
"Launch" : {
"comment" : "A tab label for the launch screen preview.",
"isCommentAutoGenerated" : true
},
"Note: iOS uses a single 1024px icon" : {
"comment" : "A note explaining that iOS uses a single 1024px icon.",
"isCommentAutoGenerated" : true
},
"Size Variants" : {
"comment" : "A heading for the size variants of an app icon.",
"isCommentAutoGenerated" : true
},
"To Export" : {
"comment" : "A section header explaining how to export branding assets.",
"isCommentAutoGenerated" : true
},
"Use the Icon Generator in Settings → DEBUG to save the 1024px icon to the Files app, then add it to Xcode's Assets.xcassets/AppIcon." : {
"comment" : "Instructions for exporting an app icon.",
"isCommentAutoGenerated" : true
},
"Xcode automatically generates all required sizes from the 1024px source." : {
"comment" : "A footnote explaining that Xcode automatically creates all necessary icon sizes from the original 1024px image.",
"isCommentAutoGenerated" : true
}
},
"version" : "1.1"