MFMailComposeViewController behaves differently in iOS 13 simulator and device - ios

I'm trying to display MFMailComposeViewController in an app.
if MFMailComposeViewController.canSendMail() {
let mailComposeViewController = MFMailComposeViewController()
mailComposeViewController.navigationBar.tintColor = .white
mailComposeViewController.mailComposeDelegate = self
mailComposeViewController.setToRecipients(["support#gmail.com"])
mailComposeViewController.setSubject("Feedback")
present(mailComposeViewController, animated: true)
} else {
print("This device is not configured to send email. Please set up an email account.")
}
In iOS 12, it shows up without an issue. In both simulator and device.
But when I run the same project on a device running iOS 13, it looks like this.
The navigation bar color is gone. Also the send button is also invisible.
So I added mailComposeViewController.navigationBar.backgroundColor = .mv_primary but it still doesn't show on the device. Strangely the background color shows in the simulator.
However there's a strange behavior. The MFMailComposeViewController immediately dismisses by itself when I run it in the simulator.
The following error also shows up in the Xcode console.
[Common] [FBSSystemService][0x5f27] Error handling open request for
com.apple.MailCompositionService: {
userInfo = {
FBSOpenApplicationRequestID = 0x5f27;
}
underlyingError = ; } 2019-11-01
14:40:05.214158+0530 MailCompose[11289:262267] [Assert] Connection
request invalidated without resuming our _serviceSessionConnection.
This is an error. 2019-11-01 14:40:05.216901+0530
MailCompose[11289:262054] [General] #CompositionServices
_serviceViewControllerReady: NSError Domain=_UIViewServiceInterfaceErrorDomain Code=0
I guess the weird dismiss error is a Xcode bug. But how do I fix the background color and the send button not showing up in the device?
This is how I set all the navigationbar related styles.
UINavigationBar.appearance().barTintColor = .mv_primary
UINavigationBar.appearance().tintColor = .white
UINavigationBar.appearance().titleTextAttributes = [NSAttributedString.Key.foregroundColor: UIColor.white]
if #available(iOS 11.0, *) {
UINavigationBar.appearance().largeTitleTextAttributes = [NSAttributedString.Key.foregroundColor: UIColor.white]
}
Demo project

Add this line of code before present it. It will work normal. This a change in iOS 13
mailController.modalPresentationStyle = .fullScreen

The reason why the Mail composer dismisses immediately is because you can't actually send an email from the simulator. The implementation is different from iOS itself.
What I guess is happening here is that while the simulator implementation uses just some normal UI elements, the MFMailComposeViewController on native iOS is actually hosted like UIDocumentPickerViewController or UIActivityViewController. This means screenshots and trying to traverse the view tree is impossible, because the view is not an actual part of your application. They do that because these controllers contain user private information. Hosted view controller do NOT allow for customization, and do not comply with your global UINavigationBar.appearance(). This would explain why it does show up in the simulator and not on your native device.

This is new UI Style from iOS 13. You can disable it in Storyboard or set manual.
Presenting modal in iOS 13 fullscreen

Related

MFMailComposeViewController is buggy on iOS 14

I recently found out that MFMailComposeViewController behaves buggy when entering the recipient email, it causes a loop going in the view as shown in the GIF below: Bug
My implementation of MFMailComposeViewController is pretty standard:
if MFMailComposeViewController.canSendMail() {
let mail = MFMailComposeViewController()
mail.mailComposeDelegate = self
mail.setSubject("Report an issue")
mail.setMessageBody("", isHTML: true)
present(mail, animated: true)
} else {
}
I have not been able to replicate this on iOS 14.6 but other users were able to replicate this behavior. They have iOS 14.3 and 14.4 on iPhone X. I think it has something to do with the iOS version or the specific iPhone model.
Is there any way I can use the simulator to debug MFMailComposeViewController functionality? Even removing canSendMail(), I can't seem to make it works with the simulator.

Dark mode not enabling/disabling when using window.overrideUserInterfaceStyle on iPad

I have successfully implemented the dark mode toggle by calling window.overrideUserInterfaceStyle in my app. When running the app in real iPhone or simulator, the function works fine.
But when I run the same script in a iPad, only the view.overrideUserInterfaceStyle works. If I try to use window.overrideUserInterfaceStyle it does not update the trait collection (and neither call traitCollectionDidChange(_).
To change the style I am doing (style from white to dark):
UIApplication.shared.connectedScenes.forEach { (scene: UIScene) in
(scene.delegate as? SceneDelegate)?.window?.overrideUserInterfaceStyle = .dark //Just this one works on iPhone.
}
UIApplication.shared.delegate?.window??.overrideUserInterfaceStyle = .dark //Force
UIWindow.appearance().overrideUserInterfaceStyle = .dark //Force
When the code is executed above, it should replace the color of all UIViews that are configured with UIColor for light and dark styles. Also call traitCollectionDidChange(_). But none of these actions are happening on the iPad simulator.
This code above only works on the iPhone real/simulator, it should work on the iPad simulator too. I don't have any iPad capable of dark style here.
Maybe it's a bug on the simulator?
I also tried to create a sample app and the style change works on iPad indeed, but since it's a clean project with no libraries it should work.
I am also trying to minimize my app, still not working so I am afraid if it's a library creating a conflict.
The problem also happens when using UIApplication setup instead of UIScene.
I am using Xcode 11.3 and Swift 5 with some Cocoapod libraries.
If you are using custom transition, I recommend you to set viewController.overrideUserInterfaceStyle = .unspecified after you finish the animation.
Like this:
if #available(iOS 13.0, *) {
viewController.overrideUserInterfaceStyle = .dark
}
UIView.animate(withDuration: defaultAnimationDuration, delay: 0, animations: {
//animate viewController
}, completion: { [weak self] _ in
if #available(iOS 13.0, *) {
viewController.overrideUserInterfaceStyle = .unspecified
}
})
This will make your UIViewController follow the UIWindow user interface style after all.

Show window that covers everything when apps enters background

In iOS12 and below I used to use something similar to this to show a window on top of everything to cover my app contents. This use to work but in iOS13 betas this does not work anymore.
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
var coverWindow: UIWindow?
func applicationDidEnterBackground(_ application: UIApplication) {
if self.coverWindow != nil {
// Skip since cover window is already showing
return
}
let vc = UIViewController()
let label = UILabel(frame: window!.bounds)
label.text = "CoverWindow. Tap to app see contents"
vc.view = label
vc.view.backgroundColor = UIColor.lightGray
let coverWindow = UIWindow(frame: window!.bounds)
coverWindow.rootViewController = vc
coverWindow.windowLevel = .alert
coverWindow.makeKeyAndVisible()
self.coverWindow = coverWindow
}
}
Apparently window changes are not reflected in screen until app enters foreground again.
Question
Does anyone know how fix or workaround this? or maybe this approach is incorrect?
Any help would be highly appreciated
Notes
I don't use a simple view because my app might be showing other windows too and my requirement is to cover everything.
I don't use applicationWillResignActive because we want to only show coverWindow when it enters background. (TouchID authentication and other stuff might trigger applicationWillResignActive and coverWindow would incorrectly show)
Example code
Download Full working example code in Github (Run in iOS simulator 12 and 13 to see the difference)
You have to implement application life cycle, you just delete it , add those app life cycle functions and implement your codes , it ll be run without error
Answer to myself.
I reported this to Apple and it was fixed in iOS 13.1 or so. Latest version of iOS13 does NOT have this bug :)

MPMediaPickerController shows an empty screen on iOS10

I am trying to port my apps to iOS 10, including the visualization of a MPMediaPickerController by means of the following code:
#IBAction func handleBrowserTapped(_ sender: AnyObject){
let pickerController = MPMediaPickerController(mediaTypes: .music)
pickerController.prompt = NSLocalizedString("Add pieces to queue", comment:"");
pickerController.allowsPickingMultipleItems=true;
pickerController.delegate=MPMusicPlayerControllerSingleton.sharedController();
self.present(pickerController, animated:true, completion:{
MPMusicPlayerControllerSingleton.sharedController().storeQueue()
})
}
Yet all that appears on the screen is a full white screen with no back buttons or other, differently from the previous iOS versions. The block is called and so the picker's presentation seems to succeed. What could be the problem?
Add Key-value to Plist :
<key>NSAppleMusicUsageDescription</key>
<string>$(app Name) uses music</string>
The issue was fixed by the latest beta now asking for an authorisation to access the iTunes library.

How to make UIBarButtonItem behave consistently for light and firm touches in iOS 8.4, using Xcode 7.2/iOS 9.2

This question has been changed due to ongoing debugging. Initially the indicators were that it was iOS8.4x vs iOS9.2 on the target issue. Upon more debugging I now believe may be an issue about touch sensitivity.
I have a refresh button in UIToolBar that I disable and grey-out while refreshing as a signifier for a somewhat long refresh operation.
The code below:
was built using Xcode 7.2/iOS 9.2
works on iOS 9.x simulators for iPad2 and iPhone 5.
does not work (the button works, executes refresh logic but does not grey out during operation) on iOS8.x iPad2 and iPhone5 devices. However it works on iPhone4 iOS 8.4 devices if the button is pressed for at least 2 seconds. On a fleeting touch the method executes, but the disable action and its visual effect (grey out) do not happen.
Hypothesis: It works a simulator since it simulates a touch event through mouse clicks.
Image below shows the correct behavior during refresh on iOS9.2 simulator
Image below shows that refresh button is not greyed out during refresh on iOS 8.4 device when touched for < 1s. The target method executes, but its disable action has no effect.
As a side-note, the iPhone5 does not have SIM but is on WiFi and network functionality of the app is correct.
Do I need to do something different so that a disabled button greys-out in iOS8.x independent of touch duration? In the code, I have comments about what I have tried and did not work. Speculation: Is it possible that some artifacts of 3D touch in iOS9.2 SDK have slipped into the application binary and making it misbehave on 8.4x phone target?
import UIKit
import QuartzCore
class MyViewController: UIViewController {
let refreshButton = UIBarButtonItem(image: UIImage(named:"refresh"),landscapeImagePhone: UIImage(named:"refresh"), style: UIBarButtonItemStyle.Plain, target: nil, action: nil)
//..other buttons.
//..... Other code that has nothing to do with refreshButton
override func viewWillAppear(animated: Bool) {
refreshButton.addTargetForAction(self, action: "performRefresh:")
let space1 = UIBarButtonItem(barButtonSystemItem:UIBarButtonSystemItem.FlexibleSpace, target: nil, action: nil)
buttonArray = [someButton, space1, refreshButton] // There are more buttons.
self.setToolbarItems(buttonArray, animated: false)
} //viewWillAppear
func performRefresh(sender: UIButton) {
NSLog("************Refresh Requested")
//sender.enabled = false //compiles, OK iOS 9, but does not grey out button on iOS8
//sender.anyFunction() // Results in exception stating that sender does not know that message.
//Switching to self.refreshButton instead.
self.refreshButton.enabled = false // OK iOS 9, but does not grey out button on iOS8
//self.refreshButton.tintColor = UIColor.blueColor // has not effect in either OS
//self.refreshButton.alpha or self.refreshButton.setNeedsDisplay() are not present.
//Refresh Logic
NSLog("************Refresh Done")
self.refreshButton.enabled = true
//I had the reciprocal operations (restore tint, set alpha to 1.0 here while debugging)
} //performRefresh
} // myViewController
I might not be understanding your question so well but maybe you can just change the buttons tint color when its disabled self.refreshButton.tintColor = [UIColor lightGrayColor]; and then change the color back when its done.

Resources