Home Articles

Force Update & show new App Version is Available

Navigating the Dynamics of Force Updates and Notifying Users about the Latest App Versions. Let us discuss by importance of Force Update in this article:

Demo of Force Update

To understand the distribution of a new version of our App that Apple has provided.

After you publish the app to the App Store, we might be required to upgrade the app and release a new version.

Importance of App Updates

Beyond the UI features, the updates play a crucial role in fortifying security, fixing bugs/issues, improvements and enhancing the overall user experience.

As developers, it is our responsibility to convey the importance of the updates in our code base.

Strategies for Non-Intrusive Notifications

In-app notifications and Push Notifications

In-App Notifications


    func showInAppNotification(message: String) {
        let alertController = UIAlertController(
            title: "Update Available",
            message: message,
            preferredStyle: .alert
        )

        let updateAction = UIAlertAction(title: "Update", style: .default) { _ in
            // Handle the action to open the App Store for the update
            if let appStoreURL = URL(string: "https://apps.apple.com/in/app/paytm-money-stocks-mf-ipo/id1344431352") {
                UIApplication.shared.open(appStoreURL, options: [:], completionHandler: nil)
            }
        }

        alertController.addAction(updateAction)

        // Present the alert
        if let viewController = UIApplication.shared.keyWindow?.rootViewController {
            viewController.present(alertController, animated: true, completion: nil)
        }
    }

    // Example usage
    let updateMessage = "A new version of the app is available. Update now for the latest features and improvements."
    showInAppNotification(message: updateMessage)
    

Push Notifications

Firebase Notification

Apple's APNS Service


    // Inside AppDelegate.swift
    func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable : Any]) {
        // Handle the received remote notification, e.g., show an in-app notification
        if let updateMessage = userInfo["updateMessage"] as? String {
            showInAppNotification(message: updateMessage)
        }
    }
    

Force-Update

Force updating the apps is typically reserved for critical situations where the security, functionality or integrity of the application is at severe risk.

Scenarios:

  1. Security Vulnerabilities — Discovered vulnerability that puts user data or privacy at risk. In rare cases during SSL pinning, the leaf node certificate has expired.
  2. Critical Bug Fix — The bug has affected the user’s performance due to some dependency or recent upgrade.
  3. Server-side changes — There are some changes in the backend system or domain changes.
  4. Deprecations of Old Versions — Developers should stay up to date on the deprecation details of certain APIs.

There are some considerations to keep in mind before asking the user for a force update.

User’s choice or consent, Potential disruption while performing some task, Limited Internet Access and Transparent communication.

Implementation

I have integrated Swift Package — NetworkKit in my project. Please take a look.

Before heading to implementation, we can use the iTunes lookup API endpoint, which gives us the latest app metadata for a given bundle identifier.

I am going to use the Apple Pages app in this example. https://itunes.apple.com/br/lookup?bundleId=361309726 → https://itunes.apple.com/br/lookup?bundleId=com.apple.Pages


    {
      "resultCount": 1,
      "results": [
        {
          "isGameCenterEnabled": false,
          "features": [
            "iosUniversal"
          ],
          "advisories": [],
          "supportedDevices": [
            "iPhone15-iPhone15",
            "iPhone15Plus-iPhone15Plus",
            "iPhone15Pro-iPhone15Pro",
            "iPhone15ProMax-iPhone15ProMax"
          ],
          "ipadScreenshotUrls": [],
          "appletvScreenshotUrls": [],
          "artworkUrl60": "https://is1-ssl.mzstatic.com/image/thumb/Purple116/v4/92/00/af/9200af99-97d8-707e-b569-ebd2b881bcfc/AppIcon-0-0-1x_U007emarketing-0-0-0-7-0-0-sRGB-0-0-0-85-220-0.png/60x60bb.jpg",
          "artworkUrl512": "https://is1-ssl.mzstatic.com/image/thumb/Purple116/v4/92/00/af/9200af99-97d8-707e-b569-ebd2b881bcfc/AppIcon-0-0-1x_U007emarketing-0-0-0-7-0-0-sRGB-0-0-0-85-220-0.png/512x512bb.jpg",
          "artworkUrl100": "https://is1-ssl.mzstatic.com/image/thumb/Purple116/v4/92/00/af/9200af99-97d8-707e-b569-ebd2b881bcfc/AppIcon-0-0-1x_U007emarketing-0-0-0-7-0-0-sRGB-0-0-0-85-220-0.png/100x100bb.jpg",
          "artistViewUrl": "https://apps.apple.com/br/developer/apple/id284417353?mt=12&uo=4",
          "screenshotUrls": [
            "https://is1-ssl.mzstatic.com/image/thumb/PurpleSource112/v4/1a/5f/1f/1a5f1fd7-d42d-aa47-d157-2ff5f49bde2c/e5cecc21-d73a-441e-87c7-d8d0b3cd0bf1_9.png/392x696bb.png"
          ],
          "kind": "software",
          "currency": "BRL",
          "releaseNotes": "• Traga uma nova dimensão aos seus documentos com objetos 3D em formato USDZ\n• Veja previsões de texto em linha enquanto você digita*\n• Comece a colaborar facilmente com outras pessoas em um documento durante uma ligação do FaceTime*\n• Encontre e abra documentos sugeridos ao buscar o Pages com o Spotlight*\n• Arraste documentos para o ícone do Pages na Tela de Início para abri-los ou importá-los*\n• Use o novo modelo Relatório Minimalista, com tipografia, cores e layout elegantes\n• Dê estilo a parágrafos com novas opções de borda e cores de fundo\n• Remova bordas externas de gráficos importados de arquivos do Microsoft Office\n\n* Requer iOS 17 ou iPadOS 17 e posteriores",
          "artistId": 284417353,
          "artistName": "Apple",
          "genres": [
            "Produtividade",
            "Negócios"
          ],
          "price": 0,
          "description": "O Pages é o processador de texto mais bonito já visto em dispositivos móveis. Comece com um modelo feito pela Apple para criar relatórios, livros digitais, currículos, pôsteres e outros documentos incríveis. Ou use um documento em branco e crie o seu próprio design. Adicione imagens, filmes, áudio, tabelas, gráficos e formas facilmente. Você pode até usar o Apple Pencil (em dispositivos compatíveis) ou o dedo para desenhar e anotar. O Pages foi feito especialmente para iPad e iPhone.\n\nUse o Apple Pencil ou o dedo para desenhar e anotar\n• Adicione desenhos com caneta, lápis, giz de cera e ferramentas de preenchimento facilmente, e anime-os para vê-los ganhar vida\n• Use a Anotação Inteligente para adicionar comentários e marcações que se mantêm ancorados ao texto associado\n• Converta palavras escritas à mão em texto com o recurso Escrever à mão e o Apple Pencil\n\nColabore com outras pessoas ao mesmo tempo\n• Com a colaboração em tempo real, toda a equipe pode trabalhar simultaneamente em um documento no Mac, iPad, iPhone e até mesmo em um PC\n• Compartilhe um documento publicamente ou com pessoas específicas, veja quem está trabalhando no documento com você e visualize os cursores de outras pessoas para seguir suas edições\n• Veja uma lista das alterações recentes em documentos colaborativos, incluindo quando uma pessoa entra, comenta e faz alterações\n• Disponível para documentos armazenados no iCloud ou no Box\n\nCrie belos documentos\n• Use a Visualização de Tela no iPhone para mostrar automaticamente textos, imagens e outros elementos otimizados para ajustar à tela\n• Escolha entre mais de 90 modelos feitos pela Apple\n• Aprimore documentos com uma biblioteca com mais de 700 formas editáveis\n• Adicione facilmente imagens, vídeo e áudio\n• Adicione uma galeria de imagens para visualizar uma coleção de fotos na mesma página\n• Crie livros EPUB interativos que podem ser compartilhados com outras pessoas ou publicados no Apple Books para download ou compra\n• Importe e edite arquivos do Microsoft Word e de texto\n\nFerramentas avançadas\n• Use a visualização de índice para navegar facilmente pelo documento ou livro\n• Adicione comentários e participe de conversas encadeadas\n• Ative o controle de alterações para marcar um documento ao editá-lo\n• Adicione marcadores para ir facilmente de uma parte a outra do documento\n• Ative páginas lado a lado para formatar o documento como páginas duplas\n• Crie modelos de página para manter a consistência do design por todo o documento de layout de página\n• Crie notas de rodapé e finais, e visualize contagens de caracteres, palavras e parágrafos\n• Adicione equações matemáticas elegantes com notação LaTeX ou MathML\n• Use o modo de apresentação para ler facilmente e rolar pelo texto automaticamente ao discursar\n\niCloud\n• Ative o iCloud para disponibilizar seus documentos automaticamente no Mac, iPad, iPhone e a partir de um navegador em um Mac ou PC em iCloud.com\n• O Pages salva os documentos automaticamente conforme você faz alterações\n\nCompartilhe uma cópia do seu trabalho\n• Use o AirDrop para enviar documentos a alguém por perto\n• Compartilhe um link do seu trabalho com rapidez e facilidade via Mail ou Mensagens\n• Exporte documentos para os formatos EPUB, Microsoft Word, RTF, TXT e PDF\n• Imprima sem fio via AirPrint, incluindo seleção do intervalo de páginas, número de cópias e impressão em frente e verso\n\nAlguns recursos podem exigir acesso à internet; tarifas e termos adicionais podem ser aplicados.",
          "genreIds": [
            "6007",
            "6000"
          ],
          "bundleId": "com.apple.Pages",
          "sellerName": "Apple Inc.",
          "releaseDate": "2010-05-26T02:18:04Z",
          "primaryGenreName": "Productivity",
          "primaryGenreId": 6007,
          "isVppDeviceBasedLicensingEnabled": true,
          "minimumOsVersion": "16.0",
          "trackId": 361309726,
          "trackName": "Pages",
          "currentVersionReleaseDate": "2023-09-21T15:37:21Z",
          "trackCensoredName": "Pages",
          "languageCodesISO2A": [
            "UK",
            "VI"
          ],
          "fileSizeBytes": "458606592",
          "sellerUrl": "http://www.apple.com/br/pages/",
          "formattedPrice": "Grátis",
          "contentAdvisoryRating": "+4",
          "averageUserRatingForCurrentVersion": 4.77975,
          "userRatingCountForCurrentVersion": 17117,
          "averageUserRating": 4.77975,
          "trackViewUrl": "https://apps.apple.com/br/app/pages/id361309726?uo=4",
          "trackContentRating": "+4",
          "version": "13.2",
          "wrapperType": "software",
          "userRatingCount": 17117
        }
      ]
    }

Model


    // MARK: - AppMetaDataResult
    struct AppMetaDataResult: Codable {
        let resultCount: Int
        let results: [AppMetaData]
    }

    // MARK: - Result
    struct AppMetaData: Codable {
        let releaseDate: String
        let releaseNotes: String
        let averageUserRatingForCurrentVersion: Double
        let trackViewUrl: URL
        let version: String
    }

    typealias AppMetaDataInfo = [AppMetaData]

ViewModel

In the ViewModel, enums of AppStatus that conform to Equatable to use the comparison of app status.

We can fetch the version number using CFBundleShortVersionString:


    Bundle.main.object(forInfoDictionaryKey: "CFBundleShortVersionString") as? String
    

To fetch the Bundle identifier of the app:


    Bundle.main.bundleIdentifier
    

In my example, I am using hardcoded values.


    final class ForceUpdateViewModel: ObservableObject {
        enum AppStatus: Equatable {
            case upToDate
            case updateAvailable(version: String, storeURL: URL, releaseNotes: String)
        }
        private var currentVersion: () -> String? = {
                  "12.1"
        }
        private let bundleIdentifier: String? = "com.apple.Pages"
    }

Swift Concurrency Async/Await


    private func fetchAppStatus() async -> AppStatus {
        do {
            let appMetaDataResult = try await networkService.sendRequest(
                endpoint: AppUpdateEndPoint.appUpdateMetaData(
                    queryParams: Constants.bundleID)) as AppMetaDataResult
            print(appMetaDataResult.results)
            guard let appMetaData = appMetaDataResult.results.first else {
                throw NetworkError.noResponse
            }
            return try updateStatus(for: appMetaData)
        } catch {
            print(error)
            return AppStatus.upToDate
        }
    }
    

Adding Network Layer, **Dependency Injection and @MainActor to the ViewModel: **


    import NetworkKit

    @MainActor
    final class ForceUpdateViewModel: ObservableObject {
        /// ..... Previous declaration 
        private var networkService: Networkable
        init(networkService: Networkable = NetworkService()) {
            self.networkService = networkService
            Task {
                appStatus = await fetchAppStatus()
            }
        }
    }
    

MainActor — A singleton actor whose executor is equivalent to the main dispatch queue.

Combine

Let’s integrate the Reactive Programming


    private func fetchAppStatusCombine() {
        networkService.sendRequest(
            endpoint: AppUpdateEndPoint.appUpdateMetaData(
                queryParams: Constants.bundleID),
            type: AppMetaDataResult.self)
            .receive(on: RunLoop.main)
            .sink { completion in
                switch completion {
                case .finished:
                    print("Finished")
                case .failure(let error):
                    print(error.customMessage)
                }
            } receiveValue: { result in
                print(result)
            }
            .store(in: &cancellable)
    }

I have added Force Update and also Apple provided an Update popup to SwiftUI View and UIKit

SwiftUI

Upon clicking the Update option in Popup, it will navigate to the AppStore.


    struct AlertView: View {
        @State private var showingAlert = false
        @State private var forceUpdate = false
        var version: String
        var storeURL: URL
        var releaseNotes: String
        var body: some View {
            VStack {
                // Button to trigger the alert with "Update" and "Cancel" buttons
                Button("Show Update Alert") {
                    forceUpdate = false
                    showingAlert.toggle()
                }

                // Button to trigger the force update alert with only "Update" button
                Button("Show Force Update Alert") {
                    forceUpdate = true
                    showingAlert.toggle()
                }
            }
            .alert(isPresented: $showingAlert) {
                // Use a ternary operator to conditionally create the Alert
                return forceUpdate
                ? Alert(title: Text("Force Update"),
                        message: Text("A new version is available. Update now?"),
                        dismissButton: .default(Text("Update"), action: {
                    handleUpdate()
                }))
                : Alert(title: Text("Update \(version)"),
                        message: Text(releaseNotes),
                        primaryButton: .default(Text("Update"), action: {
                    handleUpdate()
                }),
                        secondaryButton: .cancel())
            }
        }

        func handleUpdate() {
            UIApplication.shared.open(storeURL)
        }
    }

UIKit


    @objc func showUpdateAlert() {
        forceUpdate = false
        showAlert()
    }

    @objc func showForceUpdateAlert() {
        forceUpdate = true
        showAlert()
    }

    func showAlert() {
        switch viewModel.appStatus {
        case .upToDate:
            print("App Upto Date")
        case .updateAvailable(let version, let storeURL, let releaseNotes):
            let alertController = UIAlertController(
                title: forceUpdate ? "Force Update" : "Update \(version)",
                message: forceUpdate ? "A new version is available. Update now?": releaseNotes,
                preferredStyle: .alert
            )
            
            alertController.addAction(UIAlertAction(
                title: "Update",
                style: .default,
                handler: { _ in
                    self.handleUpdate(storeURL: storeURL)
                }
            ))
            
            if !forceUpdate {
                alertController.addAction(UIAlertAction(
                    title: "Cancel",
                    style: .cancel,
                    handler: nil
                ))
            }
            
            present(alertController, animated: true, completion: nil)
        }
    }

    func handleUpdate(storeURL: URL) {
        UIApplication.shared.open(storeURL)
    }

The entire project can be found here

This is a free third party commenting service we are using for you, which needs you to sign in to post a comment, but the good bit is you can stay anonymous while commenting.