Bedrock/Sources/Bedrock/Branding/BRANDING_GUIDE.md

22 KiB
Raw Blame History

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
  2. What's Included
  3. Step 1: Create BrandingConfig.swift
  4. Step 2: Add Launch Screen to App Entry Point
  5. Step 3: Native Launch Screen Setup
  6. Step 4: Add Branding Tools to Settings (Optional)
  7. Step 5: Generate Your App Icon
  8. Step 6: Add Icon to Xcode Assets
  9. Configuration Reference
  10. Complete Example
  11. 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

//
//  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

@main
struct YourApp: App {
    var body: some Scene {
        WindowGroup {
            ContentView()
        }
    }
}

After

import SwiftUI
import Bedrock

@main
struct YourApp: App {
    var body: some Scene {
        WindowGroup {
            ZStack {
                // Base background matching launch screen - prevents flash
                Color.Branding.primary
                    .ignoresSafeArea()
                
                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

Why the ZStack? The AppLaunchView renders your main content underneath the launch overlay from the start. Without the base Color.Branding.primary layer, the main content's background (often white) can flash briefly before the launch screen covers it. The ZStack ensures the entire window is filled with your brand color from the first frame.


Step 3: Native Launch Screen Setup

⚠️ IMPORTANT

Do NOT rely solely on INFOPLIST_KEY_UILaunchScreen_BackgroundColor. The generated UILaunchScreen dictionary may be empty, causing iOS to display a white screen before SwiftUI loads. Use a LaunchScreen.storyboard instead.

To prevent a white/black flash before the SwiftUI launch screen appears, you need to configure the native iOS launch screen to match your branding.

3.1 Create LaunchScreen.storyboard

Create a new file LaunchScreen.storyboard in your app target (e.g., YourApp/Resources/LaunchScreen.storyboard) with your theme's primary surface color as the background.

Template (replace YOUR_RED, YOUR_GREEN, YOUR_BLUE with your Color.Branding.primary RGB values):

<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="22155" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="01J-lp-oVM">
    <device id="retina6_12" orientation="portrait" appearance="light"/>
    <dependencies>
        <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="22131"/>
        <capability name="Safe area layout guides" minToolsVersion="9.0"/>
        <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
    </dependencies>
    <scenes>
        <scene sceneID="EHf-IW-A2E">
            <objects>
                <viewController id="01J-lp-oVM" sceneMemberID="viewController">
                    <view key="view" contentMode="scaleToFill" id="Ze5-6b-2t3">
                        <rect key="frame" x="0.0" y="0.0" width="393" height="852"/>
                        <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
                        <color key="backgroundColor" red="YOUR_RED" green="YOUR_GREEN" blue="YOUR_BLUE" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
                        <viewLayoutGuide key="safeArea" id="6Tk-OE-BBY"/>
                    </view>
                </viewController>
                <placeholder placeholderIdentifier="IBFirstResponder" id="iYj-Kq-Ea1" userLabel="First Responder" sceneMemberID="firstResponder"/>
            </objects>
        </scene>
    </scenes>
</document>

Example for magenta/rose branding (RGB: 0.85, 0.25, 0.45):

<color key="backgroundColor" red="0.85" green="0.25" blue="0.45" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>

3.2 Update Build Settings

In your target's build settings, add/update:

INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen

Remove these settings if present (they are unreliable):

  • INFOPLIST_KEY_UILaunchScreen_BackgroundColor
  • INFOPLIST_KEY_UILaunchScreen_BackgroundColorName
  • INFOPLIST_KEY_UILaunchScreen_Generation

3.3 Match SwiftUI Backgrounds

Ensure all views maintain the same background color for a seamless transition from the native launch screen to SwiftUI:

App Entry Point:

@main
struct YourApp: App {
    var body: some Scene {
        WindowGroup {
            ZStack {
                // Base background matching launch screen - prevents flash
                Color.Branding.primary
                    .ignoresSafeArea()
                
                AppLaunchView(config: .yourApp) {
                    MainTabView()
                }
            }
        }
    }
}

All Root Tab/Screen Views:

struct ContentView: View {
    var body: some View {
        YourContent()
            .frame(maxWidth: .infinity, maxHeight: .infinity)
            .background(Color.Branding.primary.ignoresSafeArea())
    }
}

Why Storyboard Over Generated Launch Screen?

The GENERATE_INFOPLIST_FILE = YES setting with INFOPLIST_KEY_UILaunchScreen_BackgroundColor does not reliably populate the UILaunchScreen.UIColorName key in the generated Info.plist. When this happens, iOS falls back to white (or black in dark mode).

A LaunchScreen.storyboard guarantees your color from the first frame because:

  • The color is embedded directly in the storyboard file
  • No runtime lookup of asset catalog colors is needed
  • Works consistently across all iOS versions

Important: After making these changes:

  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

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

  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 iPhoneYourApp
  3. Find AppIcon.png
  4. AirDrop or share to your Mac

Alternative (Xcode):

  1. Go to WindowDevices and Simulators
  2. Select your device/simulator
  3. Find your app → Click ⚙️ gearDownload 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.xcassetsAppIcon
  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

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

import SwiftUI
import Bedrock

@main
struct MyCameraApp: App {
    var body: some Scene {
        WindowGroup {
            ZStack {
                // Base background matching launch screen - prevents flash
                Color.Branding.primary
                    .ignoresSafeArea()
                
                AppLaunchView(config: .myCamera) {
                    ContentView()
                }
            }
        }
    }
}

Troubleshooting

White/black flash before launch screen

Cause: iOS system launch screen doesn't match your branding colors, or the generated UILaunchScreen dictionary is not being populated correctly.

Solution:

  1. Use a LaunchScreen.storyboard instead of relying on INFOPLIST_KEY_UILaunchScreen_BackgroundColor (see Step 3)
  2. Wrap AppLaunchView in a ZStack with Color.Branding.primary as the base layer (Step 2)
  3. Ensure the RGB values in the storyboard match exactly with Color.Branding.primary
  4. Remove any INFOPLIST_KEY_UILaunchScreen_* settings from your project

Critical: After any launch screen changes:

  1. Clean build (Cmd+Shift+K)
  2. Delete app from simulator/device completely
  3. Quit and restart the simulator
  4. Build and run fresh

iOS caches the launch screen aggressively.

Generated UILaunchScreen not working

Cause: The INFOPLIST_KEY_UILaunchScreen_BackgroundColor and INFOPLIST_KEY_UILaunchScreen_Generation settings do not reliably populate the UILaunchScreen dictionary in the generated Info.plist.

Solution: Use a LaunchScreen.storyboard instead. This is the only reliable method to ensure your launch screen color appears from the first frame. See Step 3.

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

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

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

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

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

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

Definitive Best Practices

To ensure every app satisfies the highest quality standards for launch and legibility:

  1. Launch Screen: Always use the LaunchScreen.storyboard (Step 3). Info.plist background colors alone are unreliable and cause a white flash.
  2. Base Layer: Wrap your App's root view in a ZStack with a Color matching your launch screen as the bottom-most layer.
  3. Color Scheme: If utilizing a dark theme, always apply .preferredColorScheme(.dark) to your root view to prevent black semantic text colors.

Summary Checklist

Branding Configuration

  • Create BrandingConfig.swift with your app's configurations
  • Define Color.Branding.primary, .secondary, and .accent
  • Create AppIconConfig extension for your app
  • Create LaunchScreenConfig extension for your app

Launch Screen Setup

  • Create LaunchScreen.storyboard with your brand color (Step 3.1)
  • Add INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen to build settings
  • Remove any INFOPLIST_KEY_UILaunchScreen_* settings if present
  • Verify RGB values in storyboard match Color.Branding.primary exactly

SwiftUI Integration

  • Add AppLaunchView wrapper to your App entry point
  • Wrap in ZStack with Color.Branding.primary as base layer
  • Add matching background to all root tab/screen views
  • Clean build, delete app, and reinstall to test launch screen

App Icon

  • (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

Happy Branding! 🎨