Skip to main content

Advanced iOS Development Techniques

Advanced SwiftUI Patterns

Custom View Builders

// @ViewBuilder for complex conditional views
struct ConditionalContentView: View {
let showDetail: Bool
let isLoading: Bool

var body: some View {
Group {
if isLoading {
LoadingView()
} else if showDetail {
DetailView()
} else {
SimpleView()
}
}
}

@ViewBuilder
private func LoadingView() -> some View {
VStack {
ProgressView()
Text("Loading...")
}
}

@ViewBuilder
private func DetailView() -> some View {
VStack(spacing: 16) {
Text("Detailed Information")
.font(.title)

ForEach(0..<5, id: \.self) { index in
DetailRow(index: index)
}
}
.padding()
}

@ViewBuilder
private func SimpleView() -> some View {
Text("Simple Content")
.font(.body)
}
}

Custom Environment Values

// Define custom environment key
struct ThemeKey: EnvironmentKey {
static let defaultValue: AppTheme = .light
}

extension EnvironmentValues {
var theme: AppTheme {
get { self[ThemeKey.self] }
set { self[ThemeKey.self] = newValue }
}
}

// Usage in views
struct ThemedView: View {
@Environment(\.theme) var theme

var body: some View {
Text("Themed Content")
.foregroundColor(theme.textColor)
.background(theme.backgroundColor)
}
}

// Inject theme into environment
struct ContentView: View {
var body: some View {
ThemedView()
.environment(\.theme, .dark)
}
}

Custom Preference Keys

// Define preference key for size tracking
struct SizePreferenceKey: PreferenceKey {
static var defaultValue: CGSize = .zero

static func reduce(value: inout CGSize, nextValue: () -> CGSize) {
value = nextValue()
}
}

// Usage for size-aware layouts
struct SizeTrackingView: View {
@State private var viewSize: CGSize = .zero

var body: some View {
VStack {
Text("Size: \(Int(viewSize.width)) x \(Int(viewSize.height))")

Rectangle()
.fill(.blue)
.frame(width: viewSize.width, height: 100)
.background(
GeometryReader { geometry in
Color.clear
.preference(key: SizePreferenceKey.self, value: geometry.size)
}
)
}
.onPreferenceChange(SizePreferenceKey.self) { size in
viewSize = size
}
}
}

Advanced State Management

Custom Observable Objects

// Advanced observable object with validation
class UserProfile: ObservableObject {
@Published var username: String = "" {
didSet {
validateUsername()
}
}

@Published var email: String = "" {
didSet {
validateEmail()
}
}

@Published var isValid: Bool = false
@Published var validationErrors: [String] = []

private func validateUsername() {
let usernameRegex = "^[a-zA-Z0-9_]{3,20}$"
let isValidUsername = username.range(of: usernameRegex, options: .regularExpression) != nil

updateValidationErrors()
}

private func validateEmail() {
let emailRegex = "[A-Z0-9a-z._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,64}"
let isValidEmail = email.range(of: emailRegex, options: .regularExpression) != nil

updateValidationErrors()
}

private func updateValidationErrors() {
var errors: [String] = []

if username.isEmpty {
errors.append("Username is required")
}

if email.isEmpty {
errors.append("Email is required")
}

validationErrors = errors
isValid = errors.isEmpty
}
}

Custom State Wrappers

// Custom state wrapper for debounced values
@propertyWrapper
struct Debounced<T: Equatable> {
private var value: T
private let delay: TimeInterval
private var timer: Timer?

var wrappedValue: T {
get { value }
set {
timer?.invalidate()
timer = Timer.scheduledTimer(withTimeInterval: delay, repeats: false) { _ in
self.value = newValue
}
}
}

init(wrappedValue: T, delay: TimeInterval = 0.5) {
self.value = wrappedValue
self.delay = delay
}
}

// Usage
struct SearchView: View {
@Debounced(delay: 0.3) private var searchText = ""
@State private var searchResults: [String] = []

var body: some View {
VStack {
TextField("Search", text: $searchText)
.textFieldStyle(.roundedBorder)

List(searchResults, id: \.self) { result in
Text(result)
}
}
.onChange(of: searchText) { newValue in
performSearch(query: newValue)
}
}

private func performSearch(query: String) {
// Search implementation
}
}

Advanced Navigation Patterns

Custom Navigation Coordinator

// Navigation coordinator for complex flows
class NavigationCoordinator: ObservableObject {
@Published var navigationPath = NavigationPath()
@Published var presentedSheet: SheetType?
@Published var presentedFullScreenCover: FullScreenType?

enum SheetType: Identifiable {
case settings, profile, help
var id: String {
switch self {
case .settings: return "settings"
case .profile: return "profile"
case .help: return "help"
}
}
}

enum FullScreenType: Identifiable {
case onboarding, tutorial
var id: String {
switch self {
case .onboarding: return "onboarding"
case .tutorial: return "tutorial"
}
}
}

func navigateToDetail(id: String) {
navigationPath.append(id)
}

func navigateToSettings() {
presentedSheet = .settings
}

func navigateToOnboarding() {
presentedFullScreenCover = .onboarding
}

func dismissSheet() {
presentedSheet = nil
}

func dismissFullScreen() {
presentedFullScreenCover = nil
}

func popToRoot() {
navigationPath = NavigationPath()
}
}

// Usage
struct MainView: View {
@StateObject private var coordinator = NavigationCoordinator()

var body: some View {
NavigationStack(path: $coordinator.navigationPath) {
HomeView()
.navigationDestination(for: String.self) { id in
DetailView(id: id)
}
}
.sheet(item: $coordinator.presentedSheet) { sheetType in
switch sheetType {
case .settings:
SettingsView()
case .profile:
ProfileView()
case .help:
HelpView()
}
}
.fullScreenCover(item: $coordinator.presentedFullScreenCover) { fullScreenType in
switch fullScreenType {
case .onboarding:
OnboardingView()
case .tutorial:
TutorialView()
}
}
.environmentObject(coordinator)
}
}

Advanced Data Flow

Custom Publishers and Subscribers

// Custom publisher for network requests
class NetworkPublisher: ObservableObject {
@Published var isLoading = false
@Published var error: Error?

func fetchData<T: Decodable>(_ type: T.Type, from url: URL) -> AnyPublisher<T, Error> {
isLoading = true
error = nil

return URLSession.shared.dataTaskPublisher(for: url)
.map(\.data)
.decode(type: T.self, decoder: JSONDecoder())
.receive(on: DispatchQueue.main)
.handleEvents(
receiveCompletion: { [weak self] completion in
self?.isLoading = false
if case .failure(let error) = completion {
self?.error = error
}
}
)
.eraseToAnyPublisher()
}
}

// Usage with Combine
struct DataView: View {
@StateObject private var networkPublisher = NetworkPublisher()
@State private var data: [Item] = []

var body: some View {
VStack {
if networkPublisher.isLoading {
ProgressView()
} else {
List(data, id: \.id) { item in
Text(item.name)
}
}
}
.onAppear {
loadData()
}
.alert("Error", isPresented: .constant(networkPublisher.error != nil)) {
Button("OK") { }
} message: {
Text(networkPublisher.error?.localizedDescription ?? "")
}
}

private func loadData() {
let url = URL(string: "https://api.example.com/items")!

networkPublisher.fetchData([Item].self, from: url)
.sink(
receiveCompletion: { _ in },
receiveValue: { [weak self] items in
self?.data = items
}
)
.store(in: &cancellables)
}
}

Advanced UI Techniques

Custom Gestures

// Custom gesture for swipe to delete with haptic feedback
struct SwipeToDeleteGesture: ViewModifier {
let onDelete: () -> Void

func body(content: Content) -> some View {
content
.gesture(
DragGesture()
.onEnded { value in
if value.translation.x < -100 {
let impactFeedback = UIImpactFeedbackGenerator(style: .medium)
impactFeedback.impactOccurred()
onDelete()
}
}
)
}
}

// Custom long press with preview
struct LongPressWithPreview: ViewModifier {
let preview: AnyView
let onLongPress: () -> Void

func body(content: Content) -> some View {
content
.onLongPressGesture(minimumDuration: 0.5) {
onLongPress()
}
.contextMenu {
preview
}
}
}

Advanced Animations

// Morphing shape animation
struct MorphingShape: View {
@State private var isExpanded = false

var body: some View {
ZStack {
Circle()
.fill(.blue)
.scaleEffect(isExpanded ? 2 : 1)
.animation(.easeInOut(duration: 1), value: isExpanded)

RoundedRectangle(cornerRadius: isExpanded ? 0 : 20)
.fill(.red)
.scaleEffect(isExpanded ? 1.5 : 0.5)
.animation(.easeInOut(duration: 1).delay(0.2), value: isExpanded)
}
.onTapGesture {
isExpanded.toggle()
}
}
}

// Staggered list animation
struct StaggeredList: View {
@State private var animate = false
let items = Array(0..<10)

var body: some View {
LazyVStack(spacing: 10) {
ForEach(items, id: \.self) { index in
Text("Item \(index)")
.padding()
.background(.blue)
.foregroundColor(.white)
.cornerRadius(10)
.offset(x: animate ? 0 : -300)
.opacity(animate ? 1 : 0)
.animation(
.easeOut(duration: 0.6)
.delay(Double(index) * 0.1),
value: animate
)
}
}
.onAppear {
animate = true
}
}
}

Performance Optimization

View Optimization Techniques

// Lazy loading with placeholder
struct LazyImageView: View {
let url: URL
@State private var image: UIImage?
@State private var isLoading = true

var body: some View {
Group {
if let image = image {
Image(uiImage: image)
.resizable()
.aspectRatio(contentMode: .fit)
} else {
Rectangle()
.fill(.gray.opacity(0.3))
.overlay(
ProgressView()
.opacity(isLoading ? 1 : 0)
)
}
}
.onAppear {
loadImage()
}
}

private func loadImage() {
URLSession.shared.dataTask(with: url) { data, _, _ in
if let data = data, let loadedImage = UIImage(data: data) {
DispatchQueue.main.async {
self.image = loadedImage
self.isLoading = false
}
}
}.resume()
}
}

// Efficient list with diffing
struct EfficientListView: View {
let items: [IdentifiableItem]

var body: some View {
LazyVStack {
ForEach(items) { item in
ItemRow(item: item)
.id(item.id) // Explicit ID for better diffing
}
}
}
}

Advanced Error Handling

// Custom error handling with retry
struct ErrorHandlingView: View {
@State private var error: AppError?
@State private var retryCount = 0

var body: some View {
Group {
if let error = error {
ErrorView(error: error) {
retryCount += 1
self.error = nil
// Retry operation
}
} else {
MainContent()
}
}
.onReceive(errorPublisher) { error in
self.error = error
}
}
}

// Custom error types
enum AppError: LocalizedError, Identifiable {
case network(Error)
case validation(String)
case unknown

var id: String {
switch self {
case .network: return "network"
case .validation: return "validation"
case .unknown: return "unknown"
}
}

var errorDescription: String? {
switch self {
case .network(let error):
return "Network error: \(error.localizedDescription)"
case .validation(let message):
return "Validation error: \(message)"
case .unknown:
return "An unknown error occurred"
}
}
}