Hello all!
I'm sorry to cross post this question (I've already done on stackoverflow: https://stackoverflow.com/questions/67349440/swifui-quick-actions-using-navigationview-and-navigationlink), but here I hope to find more fortune than there.
Code created so far:
QuickActionViewModel.swift
import Foundation
class QuickActionsViewModel: ObservableObject {
enum Action: Hashable {
case one
case two
case three
}
@Published var action: Action? = nil
}
QATestApp.swift
import CoreData
import SwiftUI
import UIKit
var shortcutItemToHandle: UIApplicationShortcutItem?
let quickActionsViewModel = QuickActionsViewModel()
@main
enum MainApp {
static func main() {
QATestApp.main()
}
}
struct QATestApp: App {
@UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
@Environment(\.scenePhase) var lifeCycle
var body: some Scene {
WindowGroup {
ContentView()
.environmentObject(quickActionsViewModel)
}
.onChange(of: lifeCycle) { newLifeCyclePhase in
if newLifeCyclePhase == .active {
guard let name = shortcutItemToHandle?.type else { return }
switch name {
case "OneAction":
quickActionsViewModel.action = .one
case "TwoAction":
quickActionsViewModel.action = .two
case "ThreeAction":
quickActionsViewModel.action = .three
default:
break
}
}
}
}
}
class AppDelegate: UIResponder, UIApplicationDelegate {
func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) - UISceneConfiguration {
let sceneConfiguration = UISceneConfiguration(name: "Custom Configuration", sessionRole: connectingSceneSession.role)
sceneConfiguration.delegateClass = SceneDelegate.self
return sceneConfiguration
}
}
class SceneDelegate: UIResponder, UIWindowSceneDelegate {
var window: UIWindow?
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
if let shortcutItem = connectionOptions.shortcutItem {
handleShortcutItem(shortcutItem)
}
}
func windowScene(_ windowScene: UIWindowScene, performActionFor shortcutItem: UIApplicationShortcutItem, completionHandler: @escaping (Bool) - Void) {
shortcutItemToHandle = shortcutItem
}
private func handleShortcutItem(_ shortcutItem: UIApplicationShortcutItem, completionHandler: ((Bool) - Void)? = nil) {}
}
ContentView.swift
import Combine
import Defaults
import SwiftUI
struct ContentView: View {
@EnvironmentObject var quickActionsViewModel: QuickActionsViewModel
var body: some View {
NavigationView {
VStack {
Home()
// quick actions links
NavigationLink(destination: Text("One"), tag: .one, selection: self.$quickActionsViewModel.action) { EmptyView() }
NavigationLink(destination: Text("Two"), tag: .two, selection: self.$quickActionsViewModel.action) { EmptyView() }
NavigationLink(destination: Text("Three"), tag: .three, selection: self.$quickActionsViewModel.action) { EmptyView() }
}
}
.navigationViewStyle(StackNavigationViewStyle())
.onReceive(self.quickActionsViewModel.$action, perform: { action in
print(action.debugDescription)
})
}
}
Home() has some stuff to see when the app opens.
I've statically create Shortcut items in the info.plist
keyUIApplicationShortcutItems/key
array
dict
keyUIApplicationShortcutItemIconSymbolName/key
stringbook.fill/string
keyUIApplicationShortcutItemTitle/key
stringOne/string
keyUIApplicationShortcutItemType/key
stringOneAction/string
/dict
dict
keyUIApplicationShortcutItemIconSymbolName/key
stringbook.fill/string
keyUIApplicationShortcutItemTitle/key
stringTwor/string
keyUIApplicationShortcutItemType/key
stringTwoAction/string
/dict
dict
keyUIApplicationShortcutItemIconSymbolName/key
stringbook.fill/string
keyUIApplicationShortcutItemTitle/key
stringThree/string
keyUIApplicationShortcutItemType/key
stringThreeAction/string
/dict
/array
Problem number one:
If I navigate away from home, send the app to background and then use one of the quick actions, none of them work.
I've added
.onReceive(self.quickActionsViewModel.$action, perform: { action in
print(action.debugDescription)
})
to check and every time prints a value
Problem number two:
if I trigger one of the quick actions the app opens and show correctly
the related view. If I send on bg again and trigger another quick
action, the app open to the old one (correct, I sent to background with
this view open), shows the Home(), show the selected view and return to
the home almost istantly, with onreceive printing NIL.
Any help is really appreciated
Post
Replies
Boosts
Views
Activity
I have this old application written in Objective C that uses CoreData, backed with ICloud Documents.
Here's the setup for the old app
@property (nonatomic, strong) PersistentStack* persistentStack;
...
- (NSManagedObjectContext *)managedObjectContext
{
if (!self.persistentStack) {
NSURL *modelURL = [[NSBundle mainBundle] URLForResource:@"MyApp" withExtension:@"momd"];
self.persistentStack = [[PersistentStack alloc] initWithStoreURL:self.storeURL modelURL:modelURL];
}
return self.persistentStack.managedObjectContext;
}
- (NSURL*)storeURL
{
NSURL* documentsDirectory = [[NSFileManager defaultManager] URLForDirectory:NSDocumentDirectory inDomain:NSUserDomainMask appropriateForURL:nil create:YES error:NULL];
return [documentsDirectory URLByAppendingPathComponent:@"MyApp.sqlite"];
}
_managedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
_managedObjectContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy;
_managedObjectContext.persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:self.managedObjectModel];
[option addEntriesFromDictionary:@{ NSPersistentStoreUbiquitousContentNameKey : @"iCloudStore" }];
[self.managedObjectContext.persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:self.storeURL options:option error:&error];
This function fetches all the data. The entity defined in the .xcdatamodeld is called Nota
+(NSMutableArray*)getNoteInContext:(NSManagedObjectContext*)context
{
if(context==nil) context = [AppDelegate mainManagedObjectContext];
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
[fetchRequest setReturnsObjectsAsFaults:NO];
NSEntityDescription *entity = [NSEntityDescription entityForName:@"Nota" inManagedObjectContext:context];
[fetchRequest setEntity:entity];
NSSortDescriptor *sortDescriptor1 = [[NSSortDescriptor alloc] initWithKey:@"timestamp" ascending:NO];
NSArray *sortDescriptors = [[NSArray alloc] initWithObjects:sortDescriptor1, nil];
[fetchRequest setSortDescriptors:sortDescriptors];
NSError *error;
NSArray *risposta = [context executeFetchRequest:fetchRequest error:&error];
return [NSMutableArray arrayWithArray:risposta];
}
Here's the "new" app, written in Swift (SwiftUI)
I defined a class to manage the core data stuff
final class CoreDataManager {
private(set) lazy var managedObjectContext: NSManagedObjectContext = {
let managedObjectContext = NSManagedObjectContext(concurrencyType: .mainQueueConcurrencyType)
managedObjectContext.persistentStoreCoordinator = self.persistentStoreCoordinator
return managedObjectContext
}()
private lazy var managedObjectModel: NSManagedObjectModel = {
guard let modelURL = Bundle.main.url(forResource: "MyApp", withExtension: "momd") else {
fatalError("Unable to Find Data Model")
}
guard let managedObjectModel = NSManagedObjectModel(contentsOf: modelURL) else {
fatalError("Unable to Load Data Model")
}
return managedObjectModel
}()
private lazy var persistentStoreCoordinator: NSPersistentStoreCoordinator = {
let persistentStoreCoordinator = NSPersistentStoreCoordinator(managedObjectModel: self.managedObjectModel)
let fileManager = FileManager.default
let storeName = "MyApp.sqlite"
do {
let documentsDirectoryURL = try fileManager.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: true)
let persistentStoreURL = documentsDirectoryURL.appendingPathComponent(storeName)
try persistentStoreCoordinator.addPersistentStore(ofType: NSSQLiteStoreType, configurationName: nil, at: persistentStoreURL, options: nil)
} catch {
fatalError("Unable to Load Persistent Store")
}
return persistentStoreCoordinator
}()
}
and here is how is consumed
struct Home: View {
@State var notes: [Nota]?
var body: some View {
VStack {
Text("Entries: \(self.notes?.count.description ?? "No entries at all")")
.padding(.top)
}.onAppear {
let moc = CoreDataManager().managedObjectContext
let fetchRequest = NSFetchRequest<Nota>()
fetchRequest.returnsObjectsAsFaults = false
let entity = NSEntityDescription.entity(forEntityName: "Nota", in: moc)
fetchRequest.entity = entity
let sortDescriptor1 = NSSortDescriptor(key: "timestamp", ascending: false)
let sortDescriptors = [sortDescriptor1]
fetchRequest.sortDescriptors = sortDescriptors
do {
self.notes = try moc.fetch(fetchRequest)
} catch {
fatalError("Failed to fetch notes: \(error)")
}
}
}
}
In the new app, having the same CFBundleIdentifier, the same Developer account and the same Apple ID in either simulator or physical device, the data saved in the "Old" App is not available in the "New" one.
If I delete the old app and reinstall it, ICloud syncs correcly show me the data from the cloud. Even after a complete reset of the device (just in case...)
What is missing? Or what I've done wrong?
Thanks in advance!
hello all!
I'm setting up a really simple media player in my swiftui app.
the code is the following:
import AVFoundation
import MediaPlayer
class AudioPlayerProvider {
private var player: AVPlayer
init() {
self.player = AVPlayer()
self.player.automaticallyWaitsToMinimizeStalling = false
self.setupAudioSession()
self.setupRemoteCommandCenter()
}
private func setupAudioSession() {
do {
try AVAudioSession.sharedInstance().setCategory(.playback, mode: .default)
try AVAudioSession.sharedInstance().setActive(true)
} catch {
print("Failed to set up audio session: \(error.localizedDescription)")
}
}
private func setupRemoteCommandCenter() {
let commandCenter = MPRemoteCommandCenter.shared()
commandCenter.playCommand.addTarget { [weak self] _ in
guard let self = self else { return .commandFailed }
self.play()
return .success
}
commandCenter.pauseCommand.addTarget { [weak self] _ in
guard let self = self else { return .commandFailed }
self.pause()
return .success
}
}
func loadAudio(from urlString: String) {
guard let url = URL(string: urlString) else { return }
let asset = AVAsset(url: url)
let playerItem = AVPlayerItem(asset: asset)
self.player.pause()
self.player.replaceCurrentItem(with: playerItem)
NotificationCenter.default.addObserver(self, selector: #selector(self.streamFinished), name: .AVPlayerItemDidPlayToEndTime, object: self.player.currentItem)
}
func setMetadata(title: String, artist: String, duration: Double) {
var nowPlayingInfo = [
MPMediaItemPropertyTitle: title,
MPMediaItemPropertyArtist: artist,
MPMediaItemPropertyPlaybackDuration: duration,
MPNowPlayingInfoPropertyPlaybackRate: 1.0,
] as [String: Any]
MPNowPlayingInfoCenter.default().nowPlayingInfo = nowPlayingInfo
}
@objc
private func streamFinished() {
self.player.seek(to: .zero)
try? AVAudioSession.sharedInstance().setActive(false)
MPNowPlayingInfoCenter.default().playbackState = .stopped
}
func play() {
MPNowPlayingInfoCenter.default().playbackState = .playing
self.player.play()
}
func pause() {
MPNowPlayingInfoCenter.default().playbackState = .paused
self.player.pause()
}
}
pretty scholastic.
The code works when called on views. It also shows up within the lock screen / dynamic island (when in background), but here lies the problems:
The play/pause button do not appear neither in the Command Center nor in the dynamic island. If I tap on the position these button should show up, the command works. Just the icons are not appearing.
the waveform animation does not animate when playing.
Many audio apps are working just fine so is my code lacking something. But I don't know why.
What is missing?
Thanks in advance!
Hello all!
I'm porting a ios15+ swiftui app to be compatible with Swift 6 and enabling strict concurrency checking gave me a warning that will be an error when switching to swift 6.
I'm initializing a persistence controller for my cloud kit container:
import CoreData
struct PersistenceController {
static let shared = PersistenceController()
let container: NSPersistentCloudKitContainer
init() {
container = NSPersistentCloudKitContainer(name: "IBreviary")
container.loadPersistentStores(completionHandler: { _, error in
if let error = error as NSError? {
fatalError("Unresolved error \(error), \(error.userInfo)")
}
})
container.viewContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy
container.viewContext.automaticallyMergesChangesFromParent = true
}
}
The warning is on the merge policy:
Reference to var 'NSMergeByPropertyObjectTrumpMergePolicy' is not concurrency-safe because it involves shared mutable state; this is an error in the Swift 6 language mode
I have no idea how to make this concurrency safe, nor I found a documentation entry to help me with this.
Anyone have idea how to solve this?
Thanks in advance
V.
Hey all!
During the migration of a production app to swift 6, I've encountered a problem: when hitting the UNUserNotificationCenter.current().requestAuthorization the app crashes.
If I switch back to Language Version 5 the app works as expected.
The offending code is defined here
class AppDelegate: NSObject, UIApplicationDelegate {
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
FirebaseApp.configure()
FirebaseConfiguration.shared.setLoggerLevel(.min)
UNUserNotificationCenter.current().delegate = self
let authOptions: UNAuthorizationOptions = [.alert, .badge, .sound]
UNUserNotificationCenter.current().requestAuthorization(options: authOptions) { _, _ in }
application.registerForRemoteNotifications()
Messaging.messaging().delegate = self
return true
}
}
The error is depicted here:
I have no idea how to fix this.
Any help will be really appreciated
thanks in advance
Hey all!
in my personal quest to make future proof apps moving to Swift 6, one of my app has a problem when setting an artwork image in MPNowPlayingInfoCenter
Here's what I'm using to set the metadata
func setMetadata(title: String? = nil, artist: String? = nil, artwork: String? = nil) async throws {
let defaultArtwork = UIImage(named: "logo")!
var nowPlayingInfo = [
MPMediaItemPropertyTitle: title ?? "***",
MPMediaItemPropertyArtist: artist ?? "***",
MPMediaItemPropertyArtwork: MPMediaItemArtwork(boundsSize: defaultArtwork.size) { _ in
defaultArtwork
}
] as [String: Any]
if let artwork = artwork {
guard let url = URL(string: artwork) else { return }
let (data, response) = try await URLSession.shared.data(from: url)
guard (response as? HTTPURLResponse)?.statusCode == 200 else { return }
guard let image = UIImage(data: data) else { return }
nowPlayingInfo[MPMediaItemPropertyArtwork] = MPMediaItemArtwork(boundsSize: image.size) { _ in
image
}
}
MPNowPlayingInfoCenter.default().nowPlayingInfo = nowPlayingInfo
}
the app crashes when hitting
MPMediaItemPropertyArtwork: MPMediaItemArtwork(boundsSize: defaultArtwork.size) { _ in
defaultArtwork
}
or
nowPlayingInfo[MPMediaItemPropertyArtwork] = MPMediaItemArtwork(boundsSize: image.size) { _ in
image
}
commenting out these two make the app work again.
Again, no clue on why.
Thanks in advance