From c7c507c8f781d0a5929f8d07e71dc0fdbe6e43e5 Mon Sep 17 00:00:00 2001 From: Matt Bruce Date: Sun, 4 Jan 2026 14:38:32 -0600 Subject: [PATCH] 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 --- Sources/Bedrock/Branding/AppIconView.swift | 231 +++++++ Sources/Bedrock/Branding/AppLaunchView.swift | 64 ++ Sources/Bedrock/Branding/BRANDING_GUIDE.md | 569 ++++++++++++++++++ .../Branding/BrandingPreviewView.swift | 91 +++ .../Bedrock/Branding/IconGeneratorView.swift | 187 ++++++ Sources/Bedrock/Branding/IconRenderer.swift | 139 +++++ .../Bedrock/Branding/LaunchScreenView.swift | 523 ++++++++++++++++ .../Bedrock/Resources/Localizable.xcstrings | 84 +++ 8 files changed, 1888 insertions(+) create mode 100644 Sources/Bedrock/Branding/AppIconView.swift create mode 100644 Sources/Bedrock/Branding/AppLaunchView.swift create mode 100644 Sources/Bedrock/Branding/BRANDING_GUIDE.md create mode 100644 Sources/Bedrock/Branding/BrandingPreviewView.swift create mode 100644 Sources/Bedrock/Branding/IconGeneratorView.swift create mode 100644 Sources/Bedrock/Branding/IconRenderer.swift create mode 100644 Sources/Bedrock/Branding/LaunchScreenView.swift diff --git a/Sources/Bedrock/Branding/AppIconView.swift b/Sources/Bedrock/Branding/AppIconView.swift new file mode 100644 index 0000000..400ce09 --- /dev/null +++ b/Sources/Bedrock/Branding/AppIconView.swift @@ -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..: 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) + } +} diff --git a/Sources/Bedrock/Branding/BRANDING_GUIDE.md b/Sources/Bedrock/Branding/BRANDING_GUIDE.md new file mode 100644 index 0000000..335dfcb --- /dev/null +++ b/Sources/Bedrock/Branding/BRANDING_GUIDE.md @@ -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! 🎨✨** diff --git a/Sources/Bedrock/Branding/BrandingPreviewView.swift b/Sources/Bedrock/Branding/BrandingPreviewView.swift new file mode 100644 index 0000000..35f3672 --- /dev/null +++ b/Sources/Bedrock/Branding/BrandingPreviewView.swift @@ -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" + ) +} diff --git a/Sources/Bedrock/Branding/IconGeneratorView.swift b/Sources/Bedrock/Branding/IconGeneratorView.swift new file mode 100644 index 0000000..90d8358 --- /dev/null +++ b/Sources/Bedrock/Branding/IconGeneratorView.swift @@ -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") +} diff --git a/Sources/Bedrock/Branding/IconRenderer.swift b/Sources/Bedrock/Branding/IconRenderer.swift new file mode 100644 index 0000000..8f42da5 --- /dev/null +++ b/Sources/Bedrock/Branding/IconRenderer.swift @@ -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) +} + diff --git a/Sources/Bedrock/Branding/LaunchScreenView.swift b/Sources/Bedrock/Branding/LaunchScreenView.swift new file mode 100644 index 0000000..758bfc8 --- /dev/null +++ b/Sources/Bedrock/Branding/LaunchScreenView.swift @@ -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.. 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) +} diff --git a/Sources/Bedrock/Resources/Localizable.xcstrings b/Sources/Bedrock/Resources/Localizable.xcstrings index 09795b3..21ffb43 100644 --- a/Sources/Bedrock/Resources/Localizable.xcstrings +++ b/Sources/Bedrock/Resources/Localizable.xcstrings @@ -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"