I am exploring the MPMediaPickerController for the first time, and I don't see anything when my button is pushed. The MPMediaPickerController.view has a frame after it is presented but it is not visible at all, nor does it appear in the view hierarchy. I am just using a simple fresh POC application with a ViewController and a IBOutlet button constrained to center vertical and horizontal, that calls this function:
func checkMediaAccess() {
let status = MPMediaLibrary.authorizationStatus()
switch status {
case .authorized:
showMediaPicker()
case .notDetermined:
MPMediaLibrary.requestAuthorization() { status in
if status == .authorized {
DispatchQueue.main.async {
self.showMediaPicker()
}
}
}
break
case .denied:
print("Denied")
break
case .restricted:
break
#unknown default:
break
}
}
Which then calls this function on authorized:
func showMediaPicker() {
print("SHOWING")
let picker = MPMediaPickerController(mediaTypes: .music)
picker.allowsPickingMultipleItems = false
picker.popoverPresentationController?.sourceView = pickerButton
picker.showsCloudItems = true
picker.delegate = self
self.present(picker, animated: true, completion: nil)
print(picker.view.frame )
}
"SHOWING" is printed, but again I don't see anything. I don't get anything else printed either.
I have all the info.plist descriptions for Media and Music access in. I also do get the prompt to allow the first time, and again "SHOWING" is printed. I have the delegates implemented, but they are never fired, because I don't see the view.
Nothing happens visually when I tap the button, and I can tap the button again.
What am i missing?
maybe the music app not installed!
Related
I've tried the solutions in other related questions but none of the answers work for me.
I've got App Tracking Transparency set up but for some reason, the dialogue box for it never shows on screen. This is the code I'm using:
func appTrackingAuthorisation() {
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
ATTrackingManager.requestTrackingAuthorization { status in
switch status {
case .authorized:
print("Enable tracking")
case .denied:
print("Deny tracking")
case .restricted:
print("Restrict (denied) tracking")
default:
print("Not determined")
}
}
}
}
In my viewDidLoad() is where I'm calling the function:
override func viewDidLoad() {
super.viewDidLoad()
appTrackingAuthorisation()
checkLocationServices()
}
I also have a location permission dialogue box show and I'm not sure if that being there is causing the ATT to not show up. I've tried putting it in AppDelegate in func applicationDidBecomeActive(_ application: UIApplication) as that's what Apple recommends but it still doesn't work. NSUserTrackingUsageDescription in Info.plist is also set. Allow apps to ask for tracking is also enabled.
I'm trying to implement the App Tracking Transparency framework, and I'm stuck, how do I load non-personalised content when the user denies the prompt.
if #available(iOS 14.5, *) {
ATTrackingManager.requestTrackingAuthorization { (status) in
switch status {
case .denied:
// What do I do here?
//GADMobileAds.sharedInstance().start(completionHandler: nil)
case .restricted, .notDetermined, .authorized:
GADMobileAds.sharedInstance().start(completionHandler: nil)
#unknown default: break
}
}
} else {
GADMobileAds.sharedInstance().start(completionHandler: nil)
}
You don't need to do anything different. If the user denies tracking then the ad framework will simply receive 0000 for the IDFA. This prevents them from identifying the user and tracking them or providing personalised ads.
if #available(iOS 14.5, *) {
ATTrackingManager.requestTrackingAuthorization { (status) in
ADMobileAds.sharedInstance().start(completionHandler: nil)
}
} else {
GADMobileAds.sharedInstance().start(completionHandler: nil)
}
You could use the .denied status to show an alert that asks them to go into settings and allow it, but don't do that.
I have one weird case where UIImagePickerController will not present.
Context: I'm using UIImagePicker to let a user pick a profile image from their photos. They tap to add a profile image and initially a modal is triggered where they can pick between using their local photos and their facebook profile photo.
This modal delegates back to the profile view controller where all the functions below exist. The modal fires didSelectProfilePhotoOption - in this case with .fromPhotos.
func didSelectProfilePhotoOption(photoSource: PhotoSource) {
switch photoSource {
case .fromFacebook:
useFacebookPhotoForProfile()
case .fromPhotos:
selectProfileImageFromPhotoLibrary()
}
}
This is important, because I have tried just triggering selectProfileImageFromPhotoLibrary from the view controller (rather than the modal delegate) and the UIImagePickerController shows every time.
func selectProfileImageFromPhotoLibrary() {
let status = PHPhotoLibrary.authorizationStatus()
handleImageAuthStatus(status: status)
}
func handleImageAuthStatus(status: PHAuthorizationStatus) {
switch status {
case .authorized:
presentImagePicker()
case .denied:
//show an error message
case .notDetermined:
PHPhotoLibrary.requestAuthorization( { status in
self.handleImageAuthStatus(status: status)
})
case .restricted:
//show an error message
}
}
func presentImagePicker() {
let imagePicker = UIImagePickerController()
imagePicker.delegate = self
imagePicker.allowsEditing = false
imagePicker.sourceType = .photoLibrary
self.present(imagePicker, animated: true, completion: nil)
}
The added weirdness is that UIImagePickerController will show when it's chained as part of requesting the photo permissions.
So in the case:
didSelectProfilePhotoOption(photoSource: .fromPhotos) >>> selectProfileImageFromPhotoLibrary >>> handleImageAuthStatus(status: .notDetermined) >>> (grant permissions) >>> handleImageAuthStatus(status: .authorized)
...UIImagePickerController shows every time.
Any ideas what's going on here? Many thanks in advance!
I am trying to video capture an ARKIt app using ReplayKit. I have a record button, when pressed turned red and start recording, then pressed again to turn white and stop recording.
But the stopRecording method never worked on the first time.
if recorder.isAvailable {
recorder.delegate = self
if recorder.isRecording {
print("Recorder is recording...")
// Stop recording
recorder.stopRecording { previewController, error in
print("Stop recording...")
self.recordImage.color = UIColor.white
self.recordImage.colorBlendFactor = 1
if let controller = previewController {
controller.previewControllerDelegate = self
self.present(controller, animated:true, completion:nil)
}
}
}
else {
// Start recording
recorder.startRecording { error in
print("Starting to record…")
if error == nil {
print("Start Recording…")
self.recordImage.color = UIColor.red
self.recordImage.colorBlendFactor = 1
}
}
}
When first pressed, I can see the recording started. Then when I pressed again, I can see that recorder.isRecording is entered, but the block in recorder.stopRecording does not work. I have to press again to start recording, then stop again before the recorder.stopRecording block is entered.
Any idea? Help is appreciated.
Press Record!
Starting to record…
Start Recording…
Press Record!
Recorder is recording...
I fixed this issue based on the replies at https://forums.developer.apple.com/thread/62624
This is definitely a bug in iOS; but removing the "Localization native development region" entry from the Info.plist seems to solve this issue.
Which iOS version are you using? I've seen cases where the completion handler doesn't get called, often on the first try, but then works thereafter. This happened a lot on iOS 9 and again in 11.0, but seems to be better in 11.0.3.
I'm not sure if you are trying this on an iPad, but your code above won't work on an iPad. You need to set a presentation style.
if UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiom.phone {
self.present(controller, animated: true, completion: nil)
}
else {
controller.popoverPresentationController?.sourceRect = self.recordingButton.bounds
controller.popoverPresentationController?.sourceView = self.view
controller.modalPresentationStyle = UIModalPresentationStyle.popover
controller.preferredContentSize = CGSize(width: self.view.frame.width, height: self.view.frame.height)
self.present(controller, animated: true, completion: nil)
}
I have a split view controller with the top view controller set to a table view controller that is to display a list of playlists for selection. The first time the app is loaded it asks for music access permission. Answering yes does give it permission but the table view displays no playlists. I end up having to kill the app and run it again. Am I asking for music library permission the wrong place? It is in that top view controller's viewWillAppear and store the playlists I'm using (since some are screened out) in a class of playlists.
override func viewWillAppear(_ animated: Bool) {
self.clearsSelectionOnViewWillAppear = self.splitViewController!.isCollapsed
super.viewWillAppear(animated)
checkMediaAccessAndSetup()
}
func checkMediaAccessAndSetup() {
let authorizationStatus = MPMediaLibrary.authorizationStatus()
switch authorizationStatus {
case .notDetermined:
// Show the permission prompt.
MPMediaLibrary.requestAuthorization({[weak self] (newAuthorizationStatus: MPMediaLibraryAuthorizationStatus) in
// Try again after the prompt is dismissed.
self?.checkMediaAccessAndSetup()
})
case .denied, .restricted:
// Do not use MPMediaQuery.
return
default:
// Proceed as usual.
break
}
// Do stuff with MPMediaQuery
self.setupPlaylistStore()
tableView.reloadData()
}
The chief problems with your code are
You are completely failing to grapple with the fact that the requestAuthorization completion function is called on a background thread. You need to step out to the main thread to do work on the interface.
You have omitted the all-important .authorized case. When you have work to do that depends upon your authorization status, you must do it now if you are authorized, but after authorization if you are not determined.
Thus, this is the correct scheme for a coherent authorization check (where f() is the thing you always want to do if you can):
let status = MPMediaLibrary.authorizationStatus()
switch status {
case .authorized:
f()
case .notDetermined:
MPMediaLibrary.requestAuthorization() { status in
if status == .authorized {
DispatchQueue.main.async {
f()
}
}
}
// ...
}
If you abstract this code into a utility method, where f can be anything, you can do this everywhere in your app where authorization might be necessary — not merely at startup.
Thanks for the comments, it gave me clues of the multi-threading that was going on and I was able to fix it with a timer call if there were no playlists in the class to keep on checking and reload the table data.
override func viewWillAppear(_ animated: Bool) {
self.clearsSelectionOnViewWillAppear = self.splitViewController!.isCollapsed
super.viewWillAppear(animated)
checkMediaAccess()
self.setupPlaylistStore()
tableView.reloadData()
if store.allPlaylists.count < 1 {
playlistTimer = Timer.scheduledTimer(timeInterval: 1.0, target: self, selector: #selector(self.playlistTimerCall), userInfo: nil, repeats: true)
}
}
#objc func playlistTimerCall() {
self.setupPlaylistStore()
if store.allPlaylists.count > 1 {
tableView.reloadData()
playlistTimer?.invalidate()
}
}
func checkMediaAccess() {
let authorizationStatus = MPMediaLibrary.authorizationStatus()
switch authorizationStatus {
case .notDetermined:
// Show the permission prompt.
MPMediaLibrary.requestAuthorization({[weak self] (newAuthorizationStatus: MPMediaLibraryAuthorizationStatus) in
// Try again after the prompt is dismissed.
self?.checkMediaAccess()
})
case .denied, .restricted:
// Do not use MPMediaQuery.
return
default:
// Proceed as usual.
break
}
}
func setupPlaylistStore() {
// purge store
store.clearAllPlaylists()
// create a query of media items in playlist
let myPlayListsQuery = MPMediaQuery.playlists()
if myPlayListsQuery.collections != nil {
playlists = myPlayListsQuery.collections!
}
// add playlists to MyPlaylist(s)
if playlists.count > 0 {
for index in 0...playlists.count - 1 {
let playlist = playlists[index]
store.addPlaylist(playlist: playlist as! MPMediaPlaylist)
}
}
var toBeRemoved = [Int]()
let defaults = UserDefaults.standard
if defaults.bool(forKey: "exclude_smart_playlists") {
//smart
for index in 0...(playlists.count - 1) {
let playlist = playlists[index]
let theAttributes = playlist.value(forProperty: MPMediaPlaylistPropertyPlaylistAttributes) as! Int
if theAttributes == 2 {
toBeRemoved.append(index)
}
}
}
if defaults.bool(forKey: "exclude_folders") {
//folders
for index in 0...(playlists.count - 1) {
let playlist = playlists[index]
let isFolder = playlist.value(forProperty: "isFolder")
let stringIsFolder = String("\(String(describing: isFolder))")
if ((stringIsFolder.range(of: "1")) != nil) {
toBeRemoved.append(index)
}
}
}
//sort from the last to the first so i don't reindex
let reverseSortedPlaylists = toBeRemoved.sorted(by: >)
// remove the unwanted playlists
for list in reverseSortedPlaylists {
store.removePlaylist(index: list)
}
}