85

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.

swiftBoy
  • 35,607
  • 26
  • 136
  • 135
johncorser
  • 9,262
  • 17
  • 57
  • 102
  • 2
    I have edited the post and I think the question that I am intending to ask is not too broad. Please reconsider closing this question, or help me out by asking for clarifying details that I am leaving out that make the scope too broad. – johncorser Oct 13 '14 at 23:14
  • 2
    I'd also like to point to the fact that [this question](http://stackoverflow.com/questions/10848/how-to-programmatically-send-sms-on-the-iphone) is the same question for Objective-C and received over 300 upvotes and was protected by moderators (not put on hold as mine has been). I believe this question could actually become quite valuable to the community. – johncorser Oct 13 '14 at 23:18
  • Converting Obj-C to Swift is not hard. A question that simply asks for a translation, citing code that has already been written, is not particularly useful. – jtbandes Oct 13 '14 at 23:35
  • 6
    Understood. I am a beginner to iOS and have not had a chance to learn Objective C. Translation may be easy once you know how it's done, but I do not know how it's done. The snippet is likely to be used by many others like me. – johncorser Oct 13 '14 at 23:44
  • There is really not much to learn. The APIs are the same. You don't need to understand the detailed syntax of Obj-C because all the classes and methods have basically the same names in Swift. https://developer.apple.com/library/ios/documentation/Swift/Conceptual/BuildingCocoaApps/InteractingWithObjective-CAPIs.html#//apple_ref/doc/uid/TP40014216-CH4-XID_25 – jtbandes Oct 13 '14 at 23:47
  • 1
    Sure, but at the very least, it seems odd to be writing Swift code and refer to Objective-C answers. The reader then has to do the conversion rather than reusing a snippet. Building up a repertoire of Swift answers on StackOverflow will only be beneficial to the community in the future, as Swift may eventually become the primary language in which iOS apps are written. – johncorser Oct 13 '14 at 23:50
  • They are really Cocoa questions and not Obj-C questions. I would encourage you to discuss the issue on Meta. http://meta.stackoverflow.com/questions/258413/how-to-handle-cross-language-questions – jtbandes Oct 13 '14 at 23:53
  • (Also keep in mind that the question you linked is 6 years old...!) – jtbandes Oct 14 '14 at 00:06
  • 2
    The question I linked is 6 years old, but the Swift language is much, much younger. The swift version of the question could not have been asked six years ago. – johncorser Oct 14 '14 at 14:59

7 Answers7

136

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
    }
}
respectTheCode
  • 42,348
  • 18
  • 73
  • 86
sivag1
  • 4,734
  • 3
  • 32
  • 35
29

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
    }
mazorati
  • 2,031
  • 3
  • 22
  • 22
18

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)
    }
}
Unknown
  • 97
  • 1
  • 2
  • 8
swiftBoy
  • 35,607
  • 26
  • 136
  • 135
  • Thank you, however the message popup does not dismiss, so I needed to add the controller to the line: controller.dismiss(animated: true, completion: nil) – David Sanford Mar 31 '20 at 13:38
10

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"

mmavliev
  • 101
  • 1
  • 6
8

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: enter image description here

Mohit Kumar
  • 2,898
  • 3
  • 21
  • 34
Pritesh Patel
  • 678
  • 17
  • 35
5

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()
Enrique
  • 1,586
  • 17
  • 14
-1
@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;
    }
}
Ved Rauniyar
  • 1,539
  • 14
  • 21
  • This will crash in a simulator, because you don't check `canSendText()`. Also, `recipients` is placed in the compose window. In most devices, MMS will allow the placeholder text to turn green, fail to match a number in contacts, and then let the user send a message to your sample text, which will go nowhere. (Possibly costing the device user a message, if the carrier charges for it.) – benc Jun 27 '19 at 17:31