MusicKit subscription page crashes

Hi there,

It seems I'm getting crashes in the Apple Music subscription view sometimes. Couldn't figure out why.

crash_info_entry_0	_MusicKit_SwiftUI/MusicSubscriptionOffer.swift:73: Fatal error: Unexpectedly changed musicSubscriptionOffer's isPresented binding to true while internal presentation state is loading(MusicSubscriptionOffer.Options(messageIdentifier: .join, itemID: 331661274, affiliateToken: ....)).

It's important to note I am displaying it from UIKit ObjC and so I had to go through some hoops in order to get this to working.

Here's the relevant code:

//
//  MusicSubscriptionOfferView.swift
//

import Foundation
import MusicKit
import Combine
import SwiftUI

struct MusicSubscriptionOfferView: View {
    @StateObject var viewModel = MusicSubscriptionOfferViewModel()

    var body: some View {
        EmptyView()
            .musicSubscriptionOffer(
                isPresented: $viewModel.isShowingOffer,
                options: viewModel.offerOptions
            )
    }
}

class MusicSubscriptionOfferViewModel: NSObject, ObservableObject {
    @Published var isShowingOffer = false
    
    static var musicItemID: MusicItemID?
    var canBecomeSubscriber = false
    
    var offerOptions: MusicSubscriptionOffer.Options {
        get {
            var options = MusicSubscriptionOffer.Options()
            options.affiliateToken = "..."
            options.itemID = MusicSubscriptionOfferViewModel.musicItemID
            return options
        }
    }

    var subscriptions = Set<AnyCancellable>()
    
    override init() {
        super.init()
        Task {
            for await subscription in MusicSubscription.subscriptionUpdates {
                MyApp.sharedInstance().canBecomeSubscriber = subscription.canBecomeSubscriber
            }
        }
        NotificationCenter.default
            .publisher(for: NSNotification.Name(K_SHOW_APPLE_MUSIC_SUBSCRIPTION))
            .sink { [weak self] _ in
                self!.isShowingOffer = true
            }
            .store(in: &subscriptions)
    }
}

MusicSubscriptionOfferViewModel is initiated on launch, so MyApp.sharedInstance().canBecomeSubscriber receives a value upon launch.

and:

@objcMembers
final class MusicOfferProxyViewController: UIHostingController<MusicSubscriptionOfferView> {
    required init() {
        super.init(rootView: MusicSubscriptionOfferView())
    }
    
    @objc required dynamic init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
}

From the UIKit view controller:

        MusicKitInterop* musicKitObject = [[MusicKitInterop alloc] init];
        [self.view addSubview:musicKitObject.musicOfferProxyViewController.view];
        [self addChildViewController:musicKitObject.musicOfferProxyViewController];
        self.musicKitInterop = musicKitObject;
class MusicKitInterop: NSObject {
    
    var musicOfferVC: MusicOfferProxyViewController
    
    override init() {
        musicOfferVC = MusicOfferProxyViewController()
    }
    
    @objc func musicOfferProxyViewController() -> UIViewController {
        return musicOfferVC
    }
    
    @objc func canBecomeSubscriber() -> Bool {
        return MyApp.sharedInstance().canBecomeSubscriber
    }
}

Replies

I have investigated a similar crash in my app before and noticed that it occurs when the options are changed while the subscription offer view is being displayed.

If the values passed to the following options are changed while the offer view is displayed, it will probably crash.

.musicSubscriptionOffer(
    isPresented: $viewModel.isShowingOffer,
    options: viewModel.offerOptions
)

In my case, it crashed when the playback song was switched while displaying it.