# Upgrade from 3.x to 3.9.0

This page introduces a new `CioAppDelegateWrapper` pattern for iOS that simplifies push notification setup and eliminates the need for method swizzling.

## Key Changes[](#key-changes)

The primary change in version 3.9.0 is the introduction of the wrapper pattern for handling push notifications on iOS. This change:

*   **Eliminates method swizzling**: No more automatic method replacement
*   **Simplifies setup**: Less boilerplate code required
*   **Improves reliability**: More predictable behavior

See the instructions below to update your app depending on whether you send push notifications with APN or FCM and whether you use UIKit or SwiftUI.

## Update with APNs[](#update-appdelegate-apn)

### UIKit[](#uikit-apn)

Update your `AppDelegate.swift` file to use the new `CioAppDelegateWrapper` pattern. See the *Before* sample to see what needs to change and the *After* sample to see the new pattern.

 Before (3.x)

#### Before (3.x)[](#Before \(3.x\))

```swift
import UIKit
import CioMessagingPushAPN

@main
class AppDelegate: UIResponder, UIApplicationDelegate {
    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
        // Override point for customization after application launch.

        // Initialize the Customer.io SDK 
        let cdpApiKey = "YOUR_CDP_API_KEY"
        let siteId = "YOUR_SITE_ID"
        let config = SDKConfigBuilder(cdpApiKey: cdpApiKey)
            // If your account is in the EU region, uncomment the next line
            // .region(.EU)
            .migrationSiteId(siteId) // only required if you used version 2.x or earlier
            .autoTrackUIKitScreenViews() // Set auto tracking of UIKit screen views
            .logLevel(CioLogLevel.debug) // Add this to troubleshoot issues - disable debug in production
        CustomerIO.initialize(withConfig: config.build())

        return true
    }
}
```

 After (3.9.0)

#### After (3.9.0)[](#After \(3.9.0\))

```swift
import UIKit
import CioMessagingPushAPN

@main
class AppDelegateWithCioIntegration: CioAppDelegateWrapper<AppDelegate> {}

class AppDelegate: UIResponder, UIApplicationDelegate {
    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
        // Override point for customization after application launch.

        // Initialize the Customer.io SDK 
        let cdpApiKey = "YOUR_CDP_API_KEY"
        let siteId = "YOUR_SITE_ID"
        let config = SDKConfigBuilder(cdpApiKey: cdpApiKey)
            // If your account is in the EU region, uncomment the next line
            // .region(.EU)
            .migrationSiteId(siteId) // only required if you used version 2.x or earlier
            .autoTrackUIKitScreenViews() // Set auto tracking of UIKit screen views
            .logLevel(CioLogLevel.debug) // Add this to troubleshoot issues - disable debug in production
        CustomerIO.initialize(withConfig: config.build())

        return true
    }
}
```

### SwiftUI[](#swiftui-apn)

If you’re using SwiftUI, you’ll need to use the `@UIApplicationDelegateAdaptor` instead of the `@main` attribute. See the *Before* sample to see what needs to change and the *After* sample to see the new pattern.

 Before (3.x)

#### Before (3.x)[](#Before \(3.x\))

```swift
import SwiftUI
import CioMessagingPushAPN

@main
struct MyApp: App {
    @UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
    
    var body: some Scene {
        WindowGroup {
            ContentView()
        }
    }
}

class AppDelegate: UIResponder, UIApplicationDelegate {
    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
        // Initialize the Customer.io SDK 
        let cdpApiKey = "YOUR_CDP_API_KEY"
        let siteId = "YOUR_SITE_ID"
        let config = SDKConfigBuilder(cdpApiKey: cdpApiKey)
            // If your account is in the EU region, uncomment the next line
            // .region(.EU)
            .migrationSiteId(siteId) // only required for migration
            .autoTrackUIKitScreenViews() // Set auto tracking of UIKit screen views
            .logLevel(CioLogLevel.debug) // Add this to troubleshoot issues - disable debug in production
        CustomerIO.initialize(withConfig: config.build())

        return true
    }
}
```

 After (3.9.0)

#### After (3.9.0)[](#After \(3.9.0\))

```swift
import SwiftUI
import CioMessagingPushAPN

@main
struct MyApp: App {
    @UIApplicationDelegateAdaptor(CioAppDelegateWrapper<AppDelegate>.self) var appDelegate
    
    var body: some Scene {
        WindowGroup {
            ContentView()
        }
    }
}

class AppDelegate: UIResponder, UIApplicationDelegate {
    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
        // Initialize the Customer.io SDK 
        let cdpApiKey = "YOUR_CDP_API_KEY"
        let siteId = "YOUR_SITE_ID"
        let config = SDKConfigBuilder(cdpApiKey: cdpApiKey)
            // If your account is in the EU region, uncomment the next line
            // .region(.EU)
            .migrationSiteId(siteId) // only required for migration
            .autoTrackUIKitScreenViews() // Set auto tracking of UIKit screen views
            .logLevel(CioLogLevel.debug) // Add this to troubleshoot issues - disable debug in production
        CustomerIO.initialize(withConfig: config.build())

        return true
    }
}
```

## Update with FCM[](#update-appdelegate-fcm)

### UIKit[](#uikit-fcm)

Update your `AppDelegate.swift` file to use the new `CioAppDelegateWrapper` pattern. See the *Before* sample to see what needs to change and the *After* sample to see the new pattern.

 Before (3.x)

#### Before (3.x)[](#Before \(3.x\))

```swift
import UIKit
import CioMessagingPushFCM
import Firebase
import FirebaseMessaging

@main
class AppDelegate: NSObject, UIApplicationDelegate {
 func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil) -> Bool {
     // Initialize the Firebase SDK.
     FirebaseApp.configure()

     let siteId = YOUR_SITE_ID
     let cdpApiKey = YOUR_CDP_API_KEY

     // Configure and initialize the Customer.io SDK
     let config = SDKConfigBuilder(cdpApiKey: cdpApiKey)
         .migrationSiteId(siteId)
         .autoTrackUIKitScreenViews()
         .autoTrackDeviceAttributes(true)
  
     CustomerIO.initialize(withConfig: config.build())

     // Initialize messaging features after initializing Customer.io SDK        
     MessagingPushFCM.initialize(
         withConfig: MessagingPushConfigBuilder()
             // optionally, configure the push module by calling functions on the builder. Such as: 
             .autoFetchDeviceToken(true)
             // See section below to find all the configuration options you can set. 
             .build()
     )

     return true
 }
}
```

 After (3.9.0)

#### After (3.9.0)[](#After \(3.9.0\))

```swift
import UIKit
import CioMessagingPushFCM
import Firebase
import FirebaseMessaging

@main
class AppDelegateWithCioIntegration: CioAppDelegateWrapper<AppDelegate> {}

class AppDelegate: NSObject, UIApplicationDelegate {
 func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil) -> Bool {
     // Initialize the Firebase SDK.
     FirebaseApp.configure()

     let siteId = YOUR_SITE_ID
     let cdpApiKey = YOUR_CDP_API_KEY

     // Configure and initialize the Customer.io SDK
     let config = SDKConfigBuilder(cdpApiKey: cdpApiKey)
         .migrationSiteId(siteId)
         .autoTrackUIKitScreenViews()
         .autoTrackDeviceAttributes(true)
  
     CustomerIO.initialize(withConfig: config.build())

     // Initialize messaging features after initializing Customer.io SDK        
     MessagingPushFCM.initialize(
         withConfig: MessagingPushConfigBuilder()
             // optionally, configure the push module by calling functions on the builder. Such as: 
             .autoFetchDeviceToken(true)
             // See section below to find all the configuration options you can set. 
             .build()
     )

     return true
 }
}
```

### SwiftUI[](#swiftui-fcm)

If you’re using SwiftUI, you’ll need to use the `@UIApplicationDelegateAdaptor` instead of the `@main` attribute. See the *Before* sample to see what needs to change and the *After* sample to see the new pattern.

 Before (3.x)

#### Before (3.x)[](#Before \(3.x\))

```swift
import SwiftUI
import CioMessagingPushFCM
import UserNotifications

@main
struct MyApp: App {
    @UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
    
    var body: some Scene {
        WindowGroup {
            ContentView()
        }
    }
}

class AppDelegate: UIResponder, UIApplicationDelegate {
    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
        // Initialize the Customer.io SDK 
        let cdpApiKey = "YOUR_CDP_API_KEY"
        let siteId = "YOUR_SITE_ID"
        let config = SDKConfigBuilder(cdpApiKey: cdpApiKey)
            // If your account is in the EU region, uncomment the next line
            // .region(.EU)
            .migrationSiteId(siteId) // only required for migration
            .autoTrackUIKitScreenViews() // Set auto tracking of UIKit screen views
            .logLevel(CioLogLevel.debug) // Add this to troubleshoot issues - disable debug in production
        CustomerIO.initialize(withConfig: config.build())

        return true
    }
}
```

 After (3.9.0)

#### After (3.9.0)[](#After \(3.9.0\))

```swift
import SwiftUI
import CioMessagingPushFCM
import UserNotifications

@main
struct MyApp: App {
    @UIApplicationDelegateAdaptor(CioAppDelegateWrapper<AppDelegate>.self) var appDelegate
    
    var body: some Scene {
        WindowGroup {
            ContentView()
        }
    }
}

class AppDelegate: UIResponder, UIApplicationDelegate {
    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
        // Initialize the Customer.io SDK 
        let cdpApiKey = "YOUR_CDP_API_KEY"
        let siteId = "YOUR_SITE_ID"
        let config = SDKConfigBuilder(cdpApiKey: cdpApiKey)
            // If your account is in the EU region, uncomment the next line
            // .region(.EU)
            .migrationSiteId(siteId) // only required for migration
            .autoTrackUIKitScreenViews() // Set auto tracking of UIKit screen views
            .logLevel(CioLogLevel.debug) // Add this to troubleshoot issues - disable debug in production
        CustomerIO.initialize(withConfig: config.build())

        return true
    }
}
```

## Important Notes[](#important-notes)

1.  `CioAppDelegateWrapper` automatically records information from following methods. But you can still use these methods if you want to add custom push handling:
    
    *   `didRegisterForRemoteNotificationsWithDeviceToken`
    *   `didFailToRegisterForRemoteNotificationsWithError`
    *   `didReceiveRemoteNotification`
    *   `userNotificationCenter(_:willPresent:withCompletionHandler:)`
    *   `userNotificationCenter(_:didReceive:withCompletionHandler:)`
    *   All other push-related delegate methods
2.  **The `@main` attribute** must be on the wrapper class, not your AppDelegate.
    

## Troubleshooting[](#troubleshooting)

If push notifications stop working after you update your implementation:

1.  Make sure that you’ve added the `@main` attribute to the wrapper class
2.  Verify that you’ve removed `@main` from your original AppDelegate
3.  Check that you’re calling `MessagingPushAPN.initialize()` or `MessagingPushFCM.initialize()`