This is a Swift 2.0 project. I have a MFMailComposeViewController that appears as expected, but does not populate the contents, and does not respond to user interaction, except for the Cancel button, which dismisses it.
The send button is greyed out. None of the text fields respond to user touch.
Running on an iPhone 6+ iOS 8.4.1, Xcode 7.0b6
What am I missing?
Here is the code I'm using:
#IBAction func hitSendEmail(sender: AnyObject) {
let emailTitle = "Title goes here"
let emailBody = "Blah blah blah blah."
let recipientAddress = "EMAILGOESHERE"
mailViewController = MFMailComposeViewController()
guard let mailvc = mailViewController else {
return
}
mailvc.mailComposeDelegate = self
mailvc.setSubject(emailTitle)
mailvc.setMessageBody(emailBody, isHTML: false)
mailvc.setToRecipients([recipientAddress])
self.presentViewController(mailvc, animated: true) { () -> Void in
}
}
func mailComposeController(controller: MFMailComposeViewController, didFinishWithResult result: MFMailComposeResult, error: NSError?) {
if let vc = controller.presentingViewController {
vc.dismissViewControllerAnimated(true, completion: { () -> Void in
self.mailViewController = nil
})
}
}
SOLVED: Apparently it's a bug in Xcode 7.0b6.
I recompiled the app under Xcode 6.4 (after changing the 'guard' statement to an 'if let') and it runs without problem.
Related
Hi I am writing the following code to refer a friend through SMS.
When I click on cell, the sms app opens with text but when again I tried for second time, it shows white color screen.
Here is my code
var controller1 = MFMessageComposeViewController()
extension ReferaFriendController:UICollectionViewDelegate,UICollectionViewDataSource,MFMessageComposeViewControllerDelegate
{
if indexPath.item == 0
{
if MFMessageComposeViewController.canSendText() {
let urlToShare = self.referalmodeldata[0].referralCodeOnly
controller1.body = "Hey I just gave an Awesome Assessment on App you can also try it. I scored , Try to beat my score \(String(describing: urlToShare))"
controller1.messageComposeDelegate = self
self.present(controller1, animated: true, completion: nil)
}
}
func messageComposeViewController(_ controller: MFMessageComposeViewController, didFinishWith result: MessageComposeResult) {
self.dismiss(animated: true, completion: nil)
}
}
As far as I can see, there's no need to keep a reference to the MFMessageComposeViewController. Just move it to be created at the point you need it, inside your if closure:
if MFMessageComposeViewController.canSendText() {
let controller = MFMessageComposeViewController()
// ...
}
I've 2 siri shortcuts in my App.
I use NSUserActivity to donate these shortcuts. I've also created 2 NSUserActivityTypes in my info.plist.
There are 2 view controllers which handle these shortcuts (1 view controller for 1 shortcut).
If I add 1 siri shortcut from 1 view controller and then go to 2nd view controller the native siri shortcut button (INUIAddVoiceShortcutButton) on 2nd view controller automatically picks the first shortcut (created from 1st view controller) and shows "Added to Siri" with suggested phrase instead of showing "Add to Siri" button. I double checked that each NSUserActivity has different identifier but still somehow its picks the wrong shortcut.
View Controller 1:
let userActivity = NSUserActivity(activityType: "com.activity.type1")
userActivity.isEligibleForSearch = true
userActivity.isEligibleForPrediction = true
userActivity.title = shortcut.title
userActivity.suggestedInvocationPhrase = suggestedPhrase
let attributes = CSSearchableItemAttributeSet(itemContentType: kUTTypeItem as String)
attributes.contentDescription = description
userActivity.contentAttributeSet = attributes
let shortcut = INShortcut(userActivity: userActivity)
let siriButton = INUIAddVoiceShortcutButton(style: .whiteOutline)
siriButton.translatesAutoresizingMaskIntoConstraints = false
siriButton.shortcut = shortcut
self.view.addSubview(siriButton)
View Controller 2:
let userActivity2 = NSUserActivity(activityType: "com.activity.type2")
userActivity2.isEligibleForSearch = true
userActivity2.isEligibleForPrediction = true
userActivity2.title = shortcut.title
userActivity2.suggestedInvocationPhrase = suggestedPhrase
let attributes = CSSearchableItemAttributeSet(itemContentType: kUTTypeItem as String)
attributes.contentDescription = description
userActivity2.contentAttributeSet = attributes
let shortcut = INShortcut(userActivity: userActivity2)
let siriButton = INUIAddVoiceShortcutButton(style: .whiteOutline)
siriButton.translatesAutoresizingMaskIntoConstraints = false
siriButton.shortcut = shortcut
self.view.addSubview(siriButton)
A similar thing happens when I delete the App and reinstall without deleting the shortcuts from Phone's Settings App.
Seems like its an IOS bug. I figured out a workaround for this problem. You have to create a new siri button every time the user add/edit the siri shortcut. Before creating siri button do the following things
1- Get all the voice shortcuts from INVoiceShortcutCenter by calling the function. Note that this happens asynchronously, so you need to do it some time before you need the data (e.g. in your AppDelegate). You'll also need to re-load this whenever the user adds a Siri Shortcut (probably in the INUIAddVoiceShortcutViewControllerDelegate.addVoiceShortcutViewController(_:didFinishWith:error) method).
INVoiceShortcutCenter.shared.getAllVoiceShortcuts { (voiceShortcutsFromCenter, error) in
guard let voiceShortcutsFromCenter = voiceShortcutsFromCenter else {
if let error = error as NSError? {
os_log("Failed to fetch voice shortcuts with error: %#", log: OSLog.default, type: .error, error)
}
return
}
self.voiceShortcuts = voiceShortcutsFromCenter
}
2- In View Controller-1 check if the shortcut is already added or not by iterating all the voice shortcuts
let voiceShorcut = voiceShortcuts.first { (voiceShortcut) -> Bool in
if let activity = voiceShortcut.shortcut.userActivity, activity.activityType == "com.activity.type1" {
return true
}
return false
}
3- If your voice shortcut is registered then pass the INShortcut to siri button otherwise don't set it.
if voiceShorcut != nil {
let shortcut = INShortcut(userActivity: userActivity1)
siriButton.shortcut = shortcut
}
Do the same thing in Second View Controller.
It's iOS 12.0 bug.
You can fix it by update INUIAddVoiceShortcutButton.voiceShortcut with correct value.
Use KVO to observe "voiceShortcut" property and when it change assign correct value to it.
I've moved to intents setup now and I find that even having just one intent setup and working the INUIAddVoiceShortcutButton is not able to track my shortcut. Once phrase is recorded it shows the Added to Siri with phrase.
But every time the app relaunches the Add to Siri button shows up instead of the Added to Siri button with recorded phrase.
I have tried going by Bilal's suggestion and although I can see the INVoiceShortcutCenter showing me my shortcut as present it doesn't loaded it into the Siri button.
My code looks like this for the button itself.
private func addSiriButton() {
let addShortcutButton = INUIAddVoiceShortcutButton(style: .blackOutline)
addShortcutButton.delegate = self
addShortcutButton.shortcut = INShortcut(intent: engine.intent )
addShortcutButton.translatesAutoresizingMaskIntoConstraints = false
siriButtonSubView.addSubview(addShortcutButton)
siriButtonSubView.centerXAnchor.constraint(equalTo: addShortcutButton.centerXAnchor).isActive = true
siriButtonSubView.centerYAnchor.constraint(equalTo: addShortcutButton.centerYAnchor).isActive = true
}
I have all the protocols implement and I had a close look at the Soup app but just can't figure out what drives this inaccuracy.
Funny enough, even British Airways app developers have given up on that as their button has exactly the same fault behaviour.
Update: I've built another test project with minimal amount implementation for the Intent and the Add to Siri and Added to Siri works perfectly. I'm guessing at this point that there is something in my own apps codebase that is causing this unwanted behaviour.
update 2 Just wanted to let everyone know I have fixed the issue. Using intents works fine but there is definitely a little sensitivity in the Intents definition file itself. All I had to do is create a new intent which then was generated and that worked. Seems my initial intent was somehow corrupt but there were no errors. After creating another intent and re-assigning intent handling function to that it all worked as intended. (pun intended)
I encountered this error when I had an existing intent and working configuration, but added a new parameter. However, in my Intent configuration, I had not added the new parameter name to a supported combination under the Shortcuts app section.
For example, if I had two properties myId and myName, and specified them as such:
let intent = MyIntent()
intent.myId = 1234
intent.myName = "banana"
Then I would need a supported combination of myId, myName in my intents definition file. In my particular case, I had forgotten myName so the INUIAddVoiceShortcutButton was attempting to do a lookup using myId, myName but didn't know how.
I just fixed this issue myself by changing my implementation (originally based on the soupchef app) to this code sample provided by apple (https://developer.apple.com/documentation/sirikit/inuiaddvoiceshortcutbutton):
EDIT: I added code that shows how I create and pass in the shortcutObject (INShortcut) for both UserActivity and custom Intent shortcuts.
The Shortcut class is an enum that contains a computed property called intent that returns an instantiation of the custom intent.
private func addShortcutButton(shortcut: Shortcut, parentViewController: UIViewController, shortcutViewControllerDelegate: INUIAddVoiceShortcutViewControllerDelegate) {
guard let view = parentViewController.view else { return }
if let intent = shortcut.intent {
shortcutObject = INShortcut(intent: intent)
} else if let userActivity = view.userActivity {
shortcutObject = INShortcut(userActivity: userActivity)
}
self.shortcutViewControllerDelegate = shortcutViewControllerDelegate
addSiriButton(to: shortcutButtonContainer)
}
func addSiriButton(to view: UIView) {
let button = INUIAddVoiceShortcutButton(style: .whiteOutline)
button.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(button)
view.centerXAnchor.constraint(equalTo: button.centerXAnchor).isActive = true
view.centerYAnchor.constraint(equalTo: button.centerYAnchor).isActive = true
button.addTarget(self, action: #selector(addToSiri(_:)), for: .touchUpInside)
}
// Present the Add Shortcut view controller after the
// user taps the "Add to Siri" button.
#objc
func addToSiri(_ sender: Any) {
guard let shortcutObject = shortcutObject else { return }
let viewController = INUIAddVoiceShortcutViewController(shortcut: shortcutObject)
viewController.modalPresentationStyle = .formSheet
viewController.delegate = shortcutViewControllerDelegate
parentViewController?.present(viewController, animated: true, completion: nil)
}
So we can't use the default Siri button, you have to use custom UIButton.
The class VoiceShortcutsManager will check all the voice intents and then we can search that list check if exist one match if yes so we should suggest edition if not we should suggest adding.
public class VoiceShortcutsManager {
private var voiceShortcuts: [INVoiceShortcut] = []
public init() {
updateVoiceShortcuts(completion: nil)
}
public func voiceShortcut(for order: DeviceIntent, powerState: State) -> INVoiceShortcut? {
for element in voiceShortcuts {
guard let intent = element.shortcut.intent as? ToggleStateIntent else {
continue
}
let deviceIntent = DeviceIntent(identifier: intent.device?.identifier, display: intent.device?.displayString ?? "")
if(order == deviceIntent && powerState == intent.state) {
return element
}
}
return nil
}
public func updateVoiceShortcuts(completion: (() -> Void)?) {
INVoiceShortcutCenter.shared.getAllVoiceShortcuts { (voiceShortcutsFromCenter, error) in
guard let voiceShortcutsFromCenter = voiceShortcutsFromCenter else {
if let error = error {
print("Failed to fetch voice shortcuts with error: \(error.localizedDescription)")
}
return
}
self.voiceShortcuts = voiceShortcutsFromCenter
if let completion = completion {
completion()
}
}
}
}
And then implement in your ViewController
class SiriAddViewController: ViewController {
let voiceShortcutManager = VoiceShortcutsManager.init()
override func viewDidLoad() {
super.viewDidLoad()
contentView.btnTest.addTarget(self, action: #selector(self.testBtn), for: .touchUpInside)
}
...
#objc func testBtn() {
let deviceIntent = DeviceIntent(identifier: smartPlug.deviceID, display: smartPlug.alias)
//is action already has a shortcut, update shortcut else create shortcut
if let shortcut = voiceShortcutManager.voiceShortcut(for: deviceIntent, powerState: .off) {
let editVoiceShortcutViewController = INUIEditVoiceShortcutViewController(voiceShortcut: shortcut)
editVoiceShortcutViewController.delegate = self
present(editVoiceShortcutViewController, animated: true, completion: nil)
} else if let shortcut = INShortcut(intent: intentTurnOff) {
let addVoiceShortcutVC = INUIAddVoiceShortcutViewController(shortcut: shortcut)
addVoiceShortcutVC.delegate = self
present(addVoiceShortcutVC, animated: true, completion: nil)
}
}
}
#available(iOS 12.0, *)
extension SiriAddViewController: INUIAddVoiceShortcutButtonDelegate {
func present(_ addVoiceShortcutViewController: INUIAddVoiceShortcutViewController, for addVoiceShortcutButton: INUIAddVoiceShortcutButton) {
addVoiceShortcutViewController.delegate = self
addVoiceShortcutViewController.modalPresentationStyle = .formSheet
present(addVoiceShortcutViewController, animated: true, completion: nil)
}
func present(_ editVoiceShortcutViewController: INUIEditVoiceShortcutViewController, for addVoiceShortcutButton: INUIAddVoiceShortcutButton) {
editVoiceShortcutViewController.delegate = self
editVoiceShortcutViewController.modalPresentationStyle = .formSheet
present(editVoiceShortcutViewController, animated: true, completion: nil)
}
}
#available(iOS 12.0, *)
extension SiriAddViewController: INUIAddVoiceShortcutViewControllerDelegate {
func addVoiceShortcutViewController(_ controller: INUIAddVoiceShortcutViewController, didFinishWith voiceShortcut: INVoiceShortcut?, error: Error?) {
voiceShortcutManager.updateVoiceShortcuts(completion: nil)
controller.dismiss(animated: true, completion: nil)
}
func addVoiceShortcutViewControllerDidCancel(_ controller: INUIAddVoiceShortcutViewController) {
controller.dismiss(animated: true, completion: nil)
}
}
#available(iOS 12.0, *)
extension SiriAddViewController: INUIEditVoiceShortcutViewControllerDelegate {
func editVoiceShortcutViewController(_ controller: INUIEditVoiceShortcutViewController, didUpdate voiceShortcut: INVoiceShortcut?, error: Error?) {
voiceShortcutManager.updateVoiceShortcuts(completion: nil)
controller.dismiss(animated: true, completion: nil)
}
func editVoiceShortcutViewController(_ controller: INUIEditVoiceShortcutViewController, didDeleteVoiceShortcutWithIdentifier deletedVoiceShortcutIdentifier: UUID) {
voiceShortcutManager.updateVoiceShortcuts(completion: nil)
controller.dismiss(animated: true, completion: nil)
}
func editVoiceShortcutViewControllerDidCancel(_ controller: INUIEditVoiceShortcutViewController) {
voiceShortcutManager.updateVoiceShortcuts(completion: nil)
controller.dismiss(animated: true, completion: nil)
}
}
}
This code was inspired/copy from this webpage:
https://www.nodesagency.com/test-drive-a-siri-shortcuts-intro/
My experience with solving this was a little different. Some intents added via the Add to Siri button worked, which adjusted to "Added to Siri", while others didn't. I realised the actions that worked didn't require parameters.
After setting default values for intents that exposed parameters, which are passed into INShortcut (and then assigned to INUIAddVoiceShortcutButton), all buttons updated their state correctly!
I have what I believe is a unique problem. I am having trouble getting my email window to dismiss. I am using Xcode 8.
The email dismisses correctly the first time I open it, but if I open it again it won't. If I press "Cancel" it does not give me the option to "Delete Draft". If I press "Send" the email is sent, but the window does not dismiss.
My code is below. The mailComposeController gets called correctly the first time, but it never gets called a second time. Does anyone have any ideas about what I am missing?
let mail = MFMailComposeViewController()
func sendEmail(body: String, subject: String) {
if MFMailComposeViewController.canSendMail() {
mail.mailComposeDelegate = self
mail.setSubject(subject)
mail.setMessageBody("\(body)", isHTML: false)
if let data = (body as NSString).data(using: String.Encoding.utf8.rawValue){
//Attach File
mail.addAttachmentData(data, mimeType: "text/plain", fileName: "data.txt")
}
present(mail, animated: true)
} else {
// show failure alert
}
}
func mailComposeController(_ controller: MFMailComposeViewController, didFinishWith result: MFMailComposeResult, error: Error?) {
controller.dismiss(animated: true, completion: nil)
}
You need to create a new MFMailComposeViewController each time. Moving your mail declaration inside sendEmail works…
func sendEmail(body: String, subject: String) {
if MFMailComposeViewController.canSendMail() {
// Create a new MFMailComposeViewController…
let mail = MFMailComposeViewController()
mail.mailComposeDelegate = self
mail.setSubject(subject)
mail.setMessageBody("\(body)", isHTML: false)
if let data = (body as NSString).data(using: String.Encoding.utf8.rawValue){
//Attach File
mail.addAttachmentData(data, mimeType: "text/plain", fileName: "data.txt")
}
present(mail, animated: true)
} else {
// show failure alert
}
}
As to why…?
I'm trying to develop an app, that take a number from a variable then open in "Messages" app with (1500) in the field To: and the variable value in Text Messages field like this
I tried this answer how to open an URL in Swift3 and Swift: How to open a new app when uibutton is tapped but i didn't figure out the URL for Messages app
what should I use? Big thanks.
use this
if MFMessageComposeViewController.canSendText() == true{
let recipients:[String] = ["1500"]
var messageController = MFMessageComposeViewController()
//messageController.messageComposeDelegate = self // implement delegate if you want
messageController.recipients = recipients
messageController.body = "Your_text"
self.present(messageController, animated: true, completion: nil)
}
You need to import "MessageUI" to your class and use the below code.
func sendMessages() {
if MFMessageComposeViewController.canSendText() == true {
let recipients:[String] = ["9895249619"]
let messageController = MFMessageComposeViewController()
messageController.messageComposeDelegate = self
messageController.recipients = recipients
messageController.body = "Your_message_text"
self.present(messageController, animated: true, completion: nil)
} else {
//handle text messaging not available
}
}
func messageComposeViewController(_ controller: MFMessageComposeViewController, didFinishWith result: MessageComposeResult) {
controller.dismiss(animated: true, completion: nil)
}
I submitted my app to the app store, but they rejected it saying that the app crashed when they clicked the feedback button which is a button that opens up a MFMailComposeViewController. The problem I am having is I have run it on many devices between the simulator and actual devices, yet I have never had this problem. I will post my functions for the feedback button below which I have called and are all connected to the button (Like I said it works completely fine every time I have tested it), and my question is: Am i doing something wrong in the code to where only they get the crash?
func giveFeedback()
{
let email = ["info#website.com"]
var fvc = view?.window?.rootViewController
var cev = MFMailComposeViewController()
cev.mailComposeDelegate = self
cev.setToRecipients(email)
cev.setSubject("MyApp")
fvc?.presentViewController(cev, animated: true, completion: nil)
}
func mailComposeController(controller: MFMailComposeViewController!, didFinishWithResult result: MFMailComposeResult, error: NSError!)
{
controller.dismissViewControllerAnimated(true, completion: nil)
}
Also, I have imported MessageUI, and in the class I have my MFMailComposeViewControllerDelegate
One thing is that you don't call canSendMail. I believe that if you try to show the MFMailComposeViewController when mails are disabled, your app would crash.
In your function you would use it for example like this:
func giveFeedback(contextViewController: UIViewController) {
if MFMailComposeViewController.canSendMail() {
let email = ["info#website.com"]
var cev = MFMailComposeViewController()
cev.mailComposeDelegate = self
cev.setToRecipients(email)
cev.setSubject("MyApp")
contextViewController.presentViewController(cev, animated: true, completion: nil)
}
}
But it would be best to check the status earlier and display the button only if email is enabled on the device...
Well I will have to guess too.... There are two things that look suspicious to me... the first thing is the line
var fvc = view?.window?.rootViewController
could you change it this way?
func giveFeedback(contextViewController: UIViewController) {
let email = ["info#website.com"]
var cev = MFMailComposeViewController()
cev.mailComposeDelegate = self
cev.setToRecipients(email)
cev.setSubject("MyApp")
contextViewController.presentViewController(cev, animated: true, completion: nil)
}