First of all, I'm really surprised that this is not a duplicate, because there are TONS of stackoverflow questions that solve this in Objective-C, but I have yet to see a good answer that used Swift.
What I'm looking for is a code snippet in Swift that sends an arbitrary string as a the body of a text message to given phone number. Essentially, I'd like something like this from Apple's official documentation, but in Swift instead of Objective-C.
I imagine this isn't too difficult, as it can be done in just a couple of lines of code in Android.
EDIT: What I'm looking for is 5-20 lines of Swift code, I do not agree that this is too broad. In Java (for Android), the solution looks like this:
package com.company.appname;
import android.app.Activity;
import android.telephony.SmsManager;
public class MainActivity extends Activity {
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
public static final mPhoneNumber = "1111111111";
public static final mMessage = "hello phone";
SmsManager.getDefault().sendTextMessage(mPhoneNumber, null, mMessage, null, null);
}
}
Now this is the android solution, and it's only 11 lines. Java tends to be much more verbose than Swift, so I doubt what I'm asking is "too broad", it is more likely that I don't know how to use the Objective-C MessageComposer object, because the documentation that I linked to above is unclear with regard to usage in Swift.
Not sure if you really got the answer. I was in a similar hunt and came across this solution and got it to work.
import UIKit
import MessageUI
class ViewController: UIViewController, MFMessageComposeViewControllerDelegate {
#IBOutlet weak var phoneNumber: UITextField!
override func viewDidLoad() {
super.viewDidLoad()
}
#IBAction func sendText(sender: UIButton) {
if (MFMessageComposeViewController.canSendText()) {
let controller = MFMessageComposeViewController()
controller.body = "Message Body"
controller.recipients = [phoneNumber.text]
controller.messageComposeDelegate = self
self.presentViewController(controller, animated: true, completion: nil)
}
}
func messageComposeViewController(controller: MFMessageComposeViewController!, didFinishWithResult result: MessageComposeResult) {
//... handle sms screen actions
self.dismissViewControllerAnimated(true, completion: nil)
}
override func viewWillDisappear(animated: Bool) {
self.navigationController?.navigationBarHidden = false
}
}
Swift 3.0 Solution:
func sendSMSText(phoneNumber: String) {
if (MFMessageComposeViewController.canSendText()) {
let controller = MFMessageComposeViewController()
controller.body = ""
controller.recipients = [phoneNumber]
controller.messageComposeDelegate = self
self.present(controller, animated: true, completion: nil)
}
}
func messageComposeViewController(_ controller: MFMessageComposeViewController, didFinishWith result: MessageComposeResult) {
//... handle sms screen actions
self.dismiss(animated: true, completion: nil)
}
override func viewWillDisappear(_ animated: Bool) {
self.navigationController?.isNavigationBarHidden = false
}
For sending iMessage in Swift 5 I use following code
Just MessageUI package and implement MFMessageComposeViewControllerDelegate
import UIKit
import MessageUI
class ViewController: UIViewController, MFMessageComposeViewControllerDelegate {
override func viewDidLoad() {
super.viewDidLoad()
}
#IBAction func sendNewIMessage(_ sender: Any) {
let messageVC = MFMessageComposeViewController()
messageVC.body = "Enter a message details here";
messageVC.recipients = ["recipients_number_here"]
messageVC.messageComposeDelegate = self
self.present(messageVC, animated: true, completion: nil)
}
func messageComposeViewController(_ controller: MFMessageComposeViewController, didFinishWith result: MessageComposeResult) {
switch (result) {
case .cancelled:
print("Message was cancelled")
case .failed:
print("Message failed")
case .sent:
print("Message was sent")
default:
return
}
dismiss(animated: true, completion: nil)
}
}
Simpler solution may be opening html link:
let mPhoneNumber = "1111111111";
let mMessage = "hello%20phone";
if let url = URL(string: "sms://" + mPhoneNumber + "&body="+mMessage) {
UIApplication.shared.open(url)
}
Make sure you replaced spaces with "%20"
Swift 3, 4, 5
#IBAction func sendSmsClick(_ sender: AnyObject) {
guard MFMessageComposeViewController.canSendText() else {
return
}
let messageVC = MFMessageComposeViewController()
messageVC.body = "Enter a message";
messageVC.recipients = ["Enter tel-nr"]
messageVC.messageComposeDelegate = self;
self.present(messageVC, animated: false, completion: nil)
}
func messageComposeViewController(_ controller: MFMessageComposeViewController, didFinishWith result: MessageComposeResult) {
switch (result.rawValue) {
case MessageComposeResult.cancelled.rawValue:
print("Message was cancelled")
self.dismiss(animated: true, completion: nil)
case MessageComposeResult.failed.rawValue:
print("Message failed")
self.dismiss(animated: true, completion: nil)
case MessageComposeResult.sent.rawValue:
print("Message was sent")
self.dismiss(animated: true, completion: nil)
default:
break;
}
}
UI will look like:
If you do not want to depend on an UIViewController, follows a Swift 3.0 solution:
import UIKit
import MessageUI
class ECMMessageComposerBuilder: NSObject {
private dynamic var customWindow: UIWindow?
private var body: String?
private var phoneNumber: String?
fileprivate var messageController: MFMessageComposeViewController?
var canCompose: Bool {
return MFMessageComposeViewController.canSendText()
}
func body(_ body: String?) -> ECMMessageComposerBuilder {
self.body = body
return self
}
func phoneNumber(_ phone: String?) -> ECMMessageComposerBuilder {
self.phoneNumber = phone
return self
}
func build() -> UIViewController? {
guard canCompose else { return nil }
messageController = MFMessageComposeViewController()
messageController?.body = body
if let phone = phoneNumber {
messageController?.recipients = [phone]
}
messageController?.messageComposeDelegate = self
return messageController
}
func show() {
customWindow = UIWindow(frame: UIScreen.main.bounds)
customWindow?.rootViewController = MNViewController()
// Move it to the top
let topWindow = UIApplication.shared.windows.last
customWindow?.windowLevel = (topWindow?.windowLevel ?? 0) + 1
// and present it
customWindow?.makeKeyAndVisible()
if let messageController = build() {
customWindow?.rootViewController?.present(messageController, animated: true, completion: nil)
}
}
func hide(animated: Bool = true) {
messageController?.dismiss(animated: animated, completion: nil)
messageController = nil
customWindow?.isHidden = true
customWindow = nil
}
}
extension ECMMessageComposerBuilder: MFMessageComposeViewControllerDelegate {
func messageComposeViewController(_ controller: MFMessageComposeViewController, didFinishWith result: MessageComposeResult) {
controller.dismiss(animated: true, completion: nil)
hide()
}
}
You call the composer this way:
let phoneNumber = "987654321"
let composer = MNMessageComposerBuilder()
composer.phoneNumber(phoneNumber).show()
or using a lazy var
let phoneNumber = "987654321"
private lazy var messageComposer: MNMessageComposerBuilder = {
let composer = MNMessageComposerBuilder()
return composer
}()
messageComposer.phoneNumber(phoneNumber).show()
#IBAction func sendMessageBtnClicked(sender: AnyObject) {
var messageVC = MFMessageComposeViewController()
messageVC.body = "Enter a message";
messageVC.recipients = ["Enter tel-nr"]
messageVC.messageComposeDelegate = self;
self.presentViewController(messageVC, animated: false, completion: nil)
}
func messageComposeViewController(controller: MFMessageComposeViewController!, didFinishWithResult result: MessageComposeResult) {
switch (result.value) {
case MessageComposeResultCancelled.value:
println("Message was cancelled")
self.dismissViewControllerAnimated(true, completion: nil)
case MessageComposeResultFailed.value:
println("Message failed")
self.dismissViewControllerAnimated(true, completion: nil)
case MessageComposeResultSent.value:
println("Message was sent")
self.dismissViewControllerAnimated(true, completion: nil)
default:
break;
}
}
Related
I have an NSObject class that consists of a basic EKCalendarChooser implementation and I am unable to get the delegate functions calendarChooserDidFinish, calendarChooserSelectionDidChange, and calendarChooserDidCancel working. I'm not sure if it is the fact that everything is in an NSObject but I wanted to keep this code separate from my other files.
I've tried a lot of troubleshooting such as not keeping the delegate methods under an extension and even making a global variable for the EKCalendarChooser as I found this post which states that non-global items could be dereferenced in contexts like this. Overall I can get the controller to pop up and it's just the way I want, but the delegate methods don't work. Below is the entire code and in my main viewController I get this to show with AddAppointments(parentViewController: self).chooseCalendarTapped()
import UIKit
import EventKitUI
class Cal: NSObject {
let eventStore = EKEventStore()
var parentViewController: UIViewController
var CalendarChooser: EKCalendarChooser = EKCalendarChooser()
init(parentViewController: UIViewController) {
self.parentViewController = parentViewController
super.init()
}
func chooseCalendarTapped() {
let authStatus = EKEventStore.authorizationStatus(for: .event)
switch authStatus {
case .authorized:
showCalendarChooser()
case .notDetermined:
requestAccess()
case .denied:
// Explain to the user that they did not give permission
break
case .restricted:
break
#unknown default:
preconditionFailure("Who knows what the future holds 🤔")
}
}
func requestAccess() {
eventStore.requestAccess(to: .event) { (granted, error) in
if granted {
// may not be called on the main thread..
DispatchQueue.main.async {
self.showCalendarChooser()
}
}
}
}
func showCalendarChooser() {
CalendarChooser = EKCalendarChooser(selectionStyle: .single, displayStyle: .allCalendars, entityType: .event, eventStore: eventStore)
// customization
CalendarChooser.showsDoneButton = true
CalendarChooser.showsCancelButton = true
// dont forget the delegate
CalendarChooser.delegate = self
let nvc = UINavigationController(rootViewController: CalendarChooser)
parentViewController.present(nvc, animated: true, completion: nil)
}
}
extension Cal : EKCalendarChooserDelegate {
func calendarChooserDidFinish(_ calendarChooser: EKCalendarChooser) {
print(calendarChooser.selectedCalendars)
// dismiss(animated: true, completion: nil)
}
func calendarChooserSelectionDidChange(_ calendarChooser: EKCalendarChooser) {
print("Changed selection")
}
func calendarChooserDidCancel(_ calendarChooser: EKCalendarChooser) {
print("Cancel tapped")
// dismiss(animated: true, completion: nil)
}
}
Check if this works now:
Declaring a var at the controller class level works here instead of having a new instance inside a function:
Controller:
class HomeVC: UIViewController {
var event = EventManager()
override func viewDidLoad() {
super.viewDidLoad()
event.chooseCalendarTapped(presentingVC: self)
}
}
EventManager Class:
import UIKit
import EventKitUI
class EventManager: NSObject {
let eventStore = EKEventStore()
override init() {
super.init()
}
weak var delegate: EKCalendarChooserDelegate? = nil
var presentingVC: UIViewController? = nil
func chooseCalendarTapped(presentingVC: UIViewController) {
self.presentingVC = presentingVC
let authStatus = EKEventStore.authorizationStatus(for: .event)
switch authStatus {
case .authorized:
showCalendarChooser()
case .notDetermined:
requestAccess()
case .denied:
// Explain to the user that they did not give permission
break
case .restricted:
break
#unknown default:
preconditionFailure("Who knows what the future holds 🤔")
}
}
func requestAccess() {
eventStore.requestAccess(to: .event) { (granted, error) in
if granted {
// may not be called on the main thread..
DispatchQueue.main.async {
self.showCalendarChooser()
}
}
}
}
func showCalendarChooser() {
let vc = EKCalendarChooser(selectionStyle: .single, displayStyle: .allCalendars, entityType: .event, eventStore: eventStore)
// customization
vc.showsDoneButton = true
vc.showsCancelButton = true
// dont forget the delegate
vc.delegate = self
let nvc = UINavigationController(rootViewController: vc)
presentingVC?.present(nvc, animated: true, completion: nil)
}
}
extension EventManager: EKCalendarChooserDelegate {
func calendarChooserDidFinish(_ calendarChooser: EKCalendarChooser) {
print(calendarChooser.selectedCalendars)
presentingVC?.dismiss(animated: true, completion: nil)
}
func calendarChooserSelectionDidChange(_ calendarChooser: EKCalendarChooser) {
print("Changed selection")
}
func calendarChooserDidCancel(_ calendarChooser: EKCalendarChooser) {
print("Cancel tapped")
presentingVC?.dismiss(animated: true, completion: nil)
}
}
Hi want want to make a refer a friend through SMS I write the following code. If user is pick on that open the SMS with text but it don't cancel again user will unable go back to app.
if indexPath.item == 1
{
//SMS
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 as? MFMessageComposeViewControllerDelegate
self.present(controller1, animated: true, completion: nil)
}
}
You haven't implemented MFMessageComposeViewControllerDelegate correctly in your code.
First of all you need to confirm MFMessageComposeViewControllerDelegate with your UIViewController as shown below:
class ViewController: UIViewController, MFMessageComposeViewControllerDelegate {
Next thing is you need add it's delegate method as shown below:
func messageComposeViewController(_ controller: MFMessageComposeViewController, didFinishWith result: MessageComposeResult) {
self.dismiss(animated: true, completion: nil)
}
Then you need to replace
controller1.messageComposeDelegate = self as? MFMessageComposeViewControllerDelegate
with
controller1.messageComposeDelegate = self
Demo code will be:
import UIKit
import MessageUI
class ViewController: UIViewController, MFMessageComposeViewControllerDelegate {
#IBAction func sendText(_ sender: Any) {
if (MFMessageComposeViewController.canSendText()) {
let controller = MFMessageComposeViewController()
controller.body = "Message Body"
controller.messageComposeDelegate = self
self.present(controller, animated: true, completion: nil)
}
}
func messageComposeViewController(_ controller: MFMessageComposeViewController, didFinishWith result: MessageComposeResult) {
self.dismiss(animated: true, completion: nil)
}
}
I am just learning to code and I am trying to create a simple code to send a message on IOS. I have made sure to import the MessageUI framework. When I run it on my simulator, it fails and shows THREAD 1 : SIGNAL SIGABRT. I apologize profusely if it's just a small mistake, as I often do such errors and don't wish to waste anyone's time. Here is my code:
import UIKit
import MessageUI
class ViewController: UIViewController, MFMessageComposeViewControllerDelegate {
#IBOutlet weak var messageLabel: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
#IBAction func SendSMS(sender: AnyObject) {
let messageVC = MFMessageComposeViewController()
messageVC.recipients = ["5146276051"]
messageVC.body = messageLabel.text
messageVC.messageComposeDelegate = self
self.presentViewController(messageVC, animated: true, completion: nil)
}
func messageComposeViewController(controller: MFMessageComposeViewController, didFinishWithResult result: MessageComposeResult) {
switch (result.rawValue) {
case MessageComposeResultCancelled.rawValue:
print("Message was cancelled")
self.dismissViewControllerAnimated(true, completion: nil)
case MessageComposeResultFailed.rawValue:
print("Message has failed")
self.dismissViewControllerAnimated(true, completion: nil)
case MessageComposeResultSent.rawValue:
print("Message was sent")
self.dismissViewControllerAnimated(true, completion: nil)
default:
break
}
}
}
Thank you very much!
Hi try to use your code on physical device, it should work.
BTW in your code please add check whether device support sending messages:
#IBAction func SendSMS(sender: AnyObject) {
//Checking whether device support message sending
if (MFMessageComposeViewController.canSendText() == false) { return }
let messageVC = MFMessageComposeViewController()
messageVC.recipients = ["5146276051"]
messageVC.body = messageLabel.text
messageVC.messageComposeDelegate = self
self.presentViewController(messageVC, animated: true, completion: nil)
}
in that way you will avoid e.g. crashes on simulator.
I'm using Xcode 8 to build an application for iPhone.My simple app has an button. When user tap on this button, messageComposeViewController is called and with phone number and message content filled.The message went through successfully when I clicked on Send button.The problem is MessageComposeViewController is showing only once.After the message sent, when I tapped on the button to call it, a black screen showed up instead of the message composer.My code is attached below. I appreciate any help.
import UIKit
import MessageUI
class ViewController: UIViewController, MFMessageComposeViewControllerDelegate {
let msg = MFMessageComposeViewController()
#IBOutlet weak var coordinate_label: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
#IBAction func sendMessage(_ sender: AnyObject) {
self.msg.body = "Message Content"
self.msg.recipients = ["xxx-xxx-xxxx"]
self.msg.messageComposeDelegate = self
self.present(msg, animated: false, completion: nil)
}
func messageComposeViewController(_ controller: MFMessageComposeViewController, didFinishWith result: MessageComposeResult) {
switch result.rawValue {
case MessageComposeResult.cancelled.rawValue:
dismiss(animated: true, completion: nil)
case MessageComposeResult.failed.rawValue:
dismiss(animated: true, completion: nil)
case MessageComposeResult.sent.rawValue:
dismiss(animated: true, completion: nil)
default:
break;
}
}
}
Try this code
import Foundation
import MessageUI
let textMessageRecipients = ["1-800-867-5309"] // for pre-populating the recipients list (optional, depending on your needs)
class MessageComposer: NSObject, MFMessageComposeViewControllerDelegate {
// A wrapper function to indicate whether or not a text message can be sent from the user's device
func canSendText() -> Bool {
return MFMessageComposeViewController.canSendText()
}
// Configures and returns a MFMessageComposeViewController instance
func configuredMessageComposeViewController() -> MFMessageComposeViewController {
let messageComposeVC = MFMessageComposeViewController()
messageComposeVC.messageComposeDelegate = self // Make sure to set this property to self, so that the controller can be dismissed!
messageComposeVC.recipients = textMessageRecipients
messageComposeVC.body = "Hey friend - Just sending a text message in-app using Swift!"
return messageComposeVC
}
// MFMessageComposeViewControllerDelegate callback - dismisses the view controller when the user is finished with it
func messageComposeViewController(controller: MFMessageComposeViewController!, didFinishWithResult result: MessageComposeResult) {
controller.dismissViewControllerAnimated(true, completion: nil)
}
}
Note, latest syntax 2017 ..
func messageComposeViewController(_ controller: MFMessageComposeViewController, didFinishWith result: MessageComposeResult) {
controller.dismiss(animated: true, completion: nil)
}
In Your viewController ..
import UIKit
class ViewController: UIViewController {
// Create a MessageComposer
let messageComposer = MessageComposer()
#IBAction func sendTextMessageButtonTapped(sender: UIButton) {
// Make sure the device can send text messages
if (messageComposer.canSendText()) {
// Obtain a configured MFMessageComposeViewController
let messageComposeVC = messageComposer.configuredMessageComposeViewController()
// Present the configured MFMessageComposeViewController instance
// Note that the dismissal of the VC will be handled by the messageComposer instance,
// since it implements the appropriate delegate call-back
presentViewController(messageComposeVC, animated: true, completion: nil)
} else {
// Let the user know if his/her device isn't able to send text messages
let errorAlert = UIAlertView(title: "Cannot Send Text Message", message: "Your device is not able to send text messages.", delegate: self, cancelButtonTitle: "OK")
errorAlert.show()
}
}
}
I am implementing a seemingly trivial and very popular use case where users select a contact, and send them a precomposed SMS.
However, the SMS ViewController dismisses itself automatically. This is easily reproducible.
How do I fix this?
Here's my code:
import UIKit
import MessageUI
import ContactsUI
class ViewController: UIViewController, MFMessageComposeViewControllerDelegate, CNContactPickerDelegate{
let contactPickerViewController = CNContactPickerViewController()
let messageVC = MFMessageComposeViewController()
override func viewDidLoad() {
super.viewDidLoad()
contactPickerViewController.delegate = self
messageVC.messageComposeDelegate = self
}
func contactPicker(picker: CNContactPickerViewController, didSelectContact contact: CNContact) {
if let phoneNumberValue = contact.phoneNumbers.first?.value as? CNPhoneNumber {
if let phoneNumber = phoneNumberValue.valueForKey("digits") as? String {
// Configure message ViewController
messageVC.recipients = [phoneNumber]
messageVC.body = "Yoyoyo"
picker.presentViewController(messageVC, animated: true, completion: nil)
}
}
}
func messageComposeViewController(controller: MFMessageComposeViewController, didFinishWithResult result: MessageComposeResult) {
self.dismissViewControllerAnimated(true, completion: nil)
}
#IBAction func invite(sender: AnyObject) {
self.presentViewController(self.contactPickerViewController, animated: true, completion: nil)
}
}
The problem is that you are asking your picker to present the message view controller. When contactPicker:picker:didSelectContact: method is called, the picker view controller is automatically being dismissed by the system. This means that the view controller is going away and you are trying to use that view controller to present your next view controller.
What you need to do is have "ViewController" in this case present the message view controller. Below is an example of the portion of your code i changed. You'll notice i have a timer there. This is because if you try to present the messageVC right away, nothing will happen because the contacts view controller isn't done dismissing itself yet.
func contactPicker(picker: CNContactPickerViewController, didSelectContact contact: CNContact) {
if let phoneNumberValue = contact.phoneNumbers.first?.value as? CNPhoneNumber {
if let phoneNumber = phoneNumberValue.valueForKey("digits") as? String {
// Configure message ViewController
messageVC.recipients = [phoneNumber]
messageVC.body = "Yoyoyo"
dispatch_async(dispatch_get_main_queue(), { () -> Void in
self.presentViewController(self.messageVC, animated: true, completion: nil)
})
}
}
}