Apple Media Library Access Permission retrieved programmatically - authorization

I would appreciated some help please even if this is maybe a trivial question.
I've written a SwiftUI app that reads the media library from the device and plays it depending on user settings. That is all fine.
The problem I have is that if you install the app for the first time, the user needs to grant permission to access the media library. This appears to be a system generated dialog but I cannot see which step in the also triggers it. I tried to have the access request be triggered code generated but that doesn't seem to trigger the pop up but it still only appears at a later stage in the app load process. The code seems to recognise though that the user reacted to the access request pop up and does select the correct switch case.
What it does not seem to do though is that it still can't read the media library. The MPMediaQuery returns nil.
My suspicion is that it somehow connected to the fact that the access request doesn't run on the main thread but I am not experienced enough in Swift programming to know what the problem is. I would be most grateful for some helpful hints.
Here is my code:
import MediaPlayer
import SwiftUI
import Foundation
class Library {
var artists : [Artist] = []
#EnvironmentObject var settings : UserSettings
var counter : Float = 0
init() {
switch MPMediaLibrary.authorizationStatus() {
case .authorized:
print("authorized")
case .denied:
print("denied")
return
case .notDetermined:
print("not determined")
MPMediaLibrary.requestAuthorization() { granted in
if granted != .authorized {
return
}
}
case .restricted:
print("restricted")
#unknown default:
print("default")
}
if MPMediaLibrary.authorizationStatus() == .notDetermined { return }
let filter : Set<MPMediaPropertyPredicate> = [MPMediaPropertyPredicate(value: MPMediaType.music.rawValue, forProperty: MPMediaItemPropertyMediaType)]
let mediaQuery = MPMediaQuery(filterPredicates: filter )
var artistsInCollection : [Artist] = []
guard let _ = mediaQuery.items?.count else { return }
for item in mediaQuery.items! {
//here I do something but that's not relevant to my question
}
self.artists = artistsInCollection
}
}

Related

How to set the google in-app comment optional?

The in-app dialog is working fine on my device but it's has required comment. the submit button will not be enable if the user does not have inputted a comment. I want the user have only optional comment on the app.
fun inAppReview() {
val reviewManager = ReviewManagerFactory.create(mContext)
val requestReviewFlow = reviewManager.requestReviewFlow()
requestReviewFlow.addOnCompleteListener { request ->
if (request.isSuccessful) {
// We got the ReviewInfo object
val reviewInfo = request.result
val flow = reviewManager.launchReviewFlow(mContext as Activity, reviewInfo)
flow.addOnCompleteListener {
// The flow has finished. The API does not indicate whether the user
// reviewed or not, or even whether the review dialog was shown. Thus, no
// matter the result, we continue our app flow.
}
} else {
Log.d("Error: ", request.exception.toString())
// There was some problem, continue regardless of the result.
}
}
}
Dependencies
implementation 'com.google.android.play:core:1.8.0'
implementation 'com.google.android.play:core-ktx:1.8.1'
As noted in the answer to a similar question https://stackoverflow.com/a/66568269/7867180: it is required only when you are a tester. Since, it is written "reviews are only visible to developers", it means that you are really the tester. In other cases, it will be optional as stated in docs.

Mac OSX app : Issue Related to NSArrayController NSTableView Core data Adding Record

I have using NSArrayController NSTableView and Core data binding.
I have take one button and connect add: method of NSArrayController to its action.
On Adding new record
TableView added and shows new record.
NSArrayController's add: method called
Problem :
Value is not added into core data (Sqlite type).
On application relaunching shows old data.
This is an apple example code. Basically it tries to save the context before app is terminated. Depending on your specific case you might move the functionality to somewhere else.
func applicationShouldTerminate(_ sender: NSApplication) -> NSApplication.TerminateReply {
// Save changes in the application's managed object context before the application terminates.
let context = persistentContainer.viewContext
if !context.commitEditing() {
NSLog("\(NSStringFromClass(type(of: self))) unable to commit editing to terminate")
return .terminateCancel
}
if !context.hasChanges {
return .terminateNow
}
do {
try context.save()
} catch {
let nserror = error as NSError
// Customize this code block to include application-specific recovery steps.
let result = sender.presentError(nserror)
if (result) {
return .terminateCancel
}
let question = NSLocalizedString("Could not save changes while quitting. Quit anyway?", comment: "Quit without saves error question message")
let info = NSLocalizedString("Quitting now will lose any changes you have made since the last successful save", comment: "Quit without saves error question info");
let quitButton = NSLocalizedString("Quit anyway", comment: "Quit anyway button title")
let cancelButton = NSLocalizedString("Cancel", comment: "Cancel button title")
let alert = NSAlert()
alert.messageText = question
alert.informativeText = info
alert.addButton(withTitle: quitButton)
alert.addButton(withTitle: cancelButton)
let answer = alert.runModal()
if answer == .alertSecondButtonReturn {
return .terminateCancel
}
}
// If we got here, it is time to quit.
return .terminateNow
}
You're probably missing setting the managedObjectContext and entityName on NSArrayController.

Having Trouble Getting the UIDocumentBrowserController to open docs in a Document based app

I've been working on a new Document-based app, and was super glad about the new UIDocumentBrowserController...trying to roll my own solution for the document browser UI was tricky!
I'm having some trouble getting the browser to open documents after they've been created.
What happens now is that when I choose to create a new document in the document browser, the document is created and opened as expected, although an error message is logged. However, after the doc is closed, I cannot reopen the file, either immediately or upon subsequent launches, even though the document is displayed. However, a weird clue here is that if I stop running the app after creating the document, but without adding new information to it (triggering the save cycle), and run the project again, I can open the file correctly. Whuch makes me think that there's something in the way the files are being saved that is the issue.
(Note: At this phase, I'm working on getting the local, non/icloud implentation working, before I move on to the icloud implementation.)
Here is the error message at any point in the code whenthe document is saved to disk (or at least most of the time!):
2017-06-20 13:21:58.254938-0500 Sermon Design 2 iOS[22454:5000138] [default] [ERROR] Could not get attribute values for item file:///Users/stevenhovater/Library/Developer/CoreSimulator/Devices/9A4364F2-B3A1-4AD9-B680-FB4BC876C707/data/Containers/Data/Application/DD534ED8-C4A3-40FE-9777-AED961976878/Documents/Untitled-9.sermon. Error: Error Domain=NSFileProviderInternalErrorDomain Code=1 "The reader is not permitted to access the URL." UserInfo={NSLocalizedDescription=The reader is not permitted to access the URL.}
I suspect that the issue lies somewher in my document types plists, which I've tried to set up by imitating the setup in the video for wwdc 2017 session 229.
My docs are encapuslated by an NSData object, using what I take to be a pretty standard subclass implentation of UIDocument. (I'm omitting the code to generate the thumbnails)
override func contents(forType typeName: String) throws -> Any {
print("Saving Document Changes")
if sermon != nil {
let newData = NSKeyedArchiver.archivedData(withRootObject: sermon!)
return newData
} else {
let newData = NSKeyedArchiver.archivedData(withRootObject: Sermon())
return newData
}
}
override func fileAttributesToWrite(to url: URL, for saveOperation: UIDocumentSaveOperation) throws -> [AnyHashable : Any] {
let thumbnail:UIImage = self.createThumbnail()
let thumbnaildict = [URLThumbnailDictionaryItem.NSThumbnail1024x1024SizeKey : thumbnail]
let dict = [URLResourceKey.thumbnailDictionaryKey:thumbnaildict]
return dict
}
override func load(fromContents contents: Any, ofType typeName: String?) throws {
guard let newSermon:Sermon = NSKeyedUnarchiver.unarchiveObject(with: contents as! Data) as? Sermon else{
throw documentErrors.invalidFile
}
self.sermon = newSermon
}
In my subclass of UIDocumentBrowserViewController, Here is my code for getting a local filename and for creating the new document.
func documentBrowser(_ controller: UIDocumentBrowserViewController, didRequestDocumentCreationWithHandler importHandler: #escaping (URL?, UIDocumentBrowserViewController.ImportMode) -> Void) {
var newDocumentURL: URL? = nil
print("creating new local document")
guard let target = self.newLocalFilename() else {
return
}
let targetSuffix = target.lastPathComponent
let tempURL = URL(fileURLWithPath: NSTemporaryDirectory() + targetSuffix)
let newDocument:SDDocument = SDDocument(fileURL: tempURL)
newDocument.sermon = Sermon()
/
newDocument.save(to: tempURL, for: .forCreating) { (saveSuccess) in
/
guard saveSuccess else {
/
importHandler(nil, .none)
return
}
/
newDocument.close(completionHandler: { (closeSuccess) in
/
guard closeSuccess else {
/
importHandler(nil, .none)
return
}
/
importHandler(tempURL, .move)
})
}
}
func newLocalFilename() -> URL? {
let fileManager = FileManager()
guard let baseURL = self.localDocumentsDirectoryURL.appendingPathComponent("Untitled")
else {return nil}
var target = baseURL.appendingPathExtension(DocumentBrowserViewController.documentExtension)
var nameSuffix = 2
while fileManager.fileExists(atPath: target.path) {
target = URL(fileURLWithPath: baseURL.path + "-\(nameSuffix).\(DocumentBrowserViewController.documentExtension)")
nameSuffix += 1
}
let targetSuffix = target.lastPathComponent
print("Target name: \(targetSuffix)")
print("new url: \(target)")
return target
}
After four or five hours of work banging my head against this problem, I discovered a simple solution: don't test in the Simulator. I switched to testing on my device and instantly everything started working as advertised.
[I can't speak from experience here, but it may be that the "doesn't work in the Simulator" problem is confined to Sierra, but that the Simulator does work in High Sierra. This would explain why some users see this issue and others don't, and especially why Apple seems blissfully unaware of it in the WWDC video.]
I had exactly the same issue when I was trying to save to NSTemporaryDirectory().
If you instead save to the documents directory ([[[NSFileManager defaultManager] URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask] firstObject]), it appears to work fine!
Update: it looks like this issue is fixed in iOS 11 beta 3, and you can now save newly created documents to NSTemporaryDirectory() correctly.
Here is my current theory.
This error
Error Domain=NSFileProviderInternalErrorDomain Code=1 "The reader is not permitted to access the URL."
shows up when first creating a UIDocument at a new URL using -initWithFileURL. It's basically saying "this URL doesn't exist yet," but in a way that makes it sound more like a permissions issue.
As far as I can tell, it doesn't prevent you from saving, opening, editing, or closing the file. So I think it's just a superfluous error that Apple should have taken out.
I found that the error happens on simulator when LSSupportsOpeningDocumentsInPlace property is set to YES in the info.plist.
Turn this property to NO, then it starts working, in my case.
On the real device, it works anyway without having error.
Had similar problem, and tried the method for providing a default Core Location in the Schemes settings and it works now. The method was mentioned in this answer: IOS 9 Error Domain=kCLErrorDomain Code=0 "(null)"

XCode UITesting check if a text field exists

I can't find any way to check if a text field exists without trying to get it which then fails the tests and shows an error if it can't be found. No matches found for TextField
Current code
XCUIElement *usernameTextField = app.textFields[#"username"];
Reason/detail
I've got a Objective C UITest in XCode which logs into my app in setUp and logs out in tearDown however sometimes my app is already logged in when the test starts (if the simulator has been used for anything else in the meantime). I'd like to be able to check to see if the username textfield exists in my setUp and then if it doesn't I can skip the login or call my logout function and continue as normal.
Not sure about Obj-C but here's how it would work in Swift.
let usernameTextField = app.textFields["username"]
if usernameTextField.exists {
do something
} else {
do something else
}
Here is the code in Swift, which can easily be converted to Obj-C:
// given:
// usernameTextField exists
// The username that is possibly entered there is "username".
// then:
if usernameTextField.value as! String == "username" {
// logged in
} else {
// not logged in
}

Checking authorizations for MPMediaLibrary in Swift 3

I am using the following code to check the MPMediaLibrary authorizations:
func handlePermissions() {
let permissionStatus = MPMediaLibrary.authorizationStatus()
switch (permissionStatus) {
case MPMediaLibraryAuthorizationStatus.authorized:
print("permission status is authorized")
case MPMediaLibraryAuthorizationStatus.notDetermined:
print("permission status is not determined")
MPMediaLibrary.requestAuthorization(MPMediaLibraryAuthorizationStatus -> permissionStatus)
case MPMediaLibraryAuthorizationStatus.denied:
print("permission status is denied")
case MPMediaLibraryAuthorizationStatus.restricted:
print("permission status is restricted")
}
}
Ultimately, I am trying to prompt the user for their authorization (upon launch) prior to calling a query...via the case MPMediaLibraryAuthorizationStatus.notDetermined:. The code above produces the error: Expected type after '->'. When the requestAuthorization() line is commented out, the app crashes upon launch (access has not been authorized) and the authorization prompt view is shown after the launch screen disappears.
I've seen some examples of how to perform requestAuthorization() in Objective C but nothing in Swift. I don't understand:
MPMediaLibrary.requestAuthorization( handler: (MPMediaLibraryAuthorizationStatus) -> Void )
What is the proper way to request authorization for access to the MPMediaLibrary in Swift 3?
You've actually used the prototype of the requestAuthorization method. You need to adapt it to your own use.
MPMediaLibrary.requestAuthorization( handler: (MPMediaLibraryAuthorizationStatus) -> Void )
means that requestAuthorization take a function as parameter and this function takes a MPMediaLibraryAuthorizationStatus as parameter an return nothing.
For example if I want to request the authorisation and then display the result inside my console. I first check if the application is not already authorised :
if authoriationStatus != .authorized {
MPMediaLibrary.requestAuthorization({
(status) in
switch status {
case .notDetermined:
print("notDetermined")
case .denied:
print("denied")
case .restricted:
print("restricted")
case .authorized:
print("authorized")
}
})
}
As you can see, I used a function as a parameter for the method requestAuthorization. The function is described inside {...}. It's called a closure and it's something you always use in Swift.
For swift 4.2 to check authorisations for MPMediaLibrary
import MediaPlayer
let status = MPMediaLibrary.authorizationStatus()
switch status {
case .authorized:
self.openMusicLibrary()
break
case .notDetermined:
MPMediaLibrary.requestAuthorization() { status in
if status == .authorized {
DispatchQueue.main.async {
self.openMusicLibrary()
}
}
}
break
case .denied:
//show alert
print("Please Allow Access to the Media & Apple Music from appliction setting.")
break
case .restricted:
break
}