본문 바로가기

Services

Email 보내기 기능 구현하기 : UIKit

본 포스팅은 Swift 5.2.4 기준으로 작성되었습니다.

오늘은 앱 내에서 Email 을 보낼 수 있는 기능에 대해 공부해보도록 하겠습니다. 이메일 보내기 기능은 Apple 기본 메일 앱에서 제공하는 보내기 폼을 이용하는 방식이라 그렇게 어려운 부분은 없는것 같더라고요 ㅎㅎ 다만 Apple 기본메일이 아이폰에 설치되어 있지 않거나 깔려있더라도 로그인과 같은 기본 세팅을 하지않은 사용자라면 이 보내기 폼을 사용할 수 없습니다. 그럼 바로 시작할게요~~


MessageUI 살펴보기

일단 앱 내에서 이메일을 보낼 수 있도록 구현하려면 MessageUI Framework 를 이용해야 합니다.

 

 

MessageUI 는 이메일 또는 문자메세지를 앱 내에서 작성하고 보낼 수 있도록 도와주는 Framework 이며, 프레임워크에서 제공하는 Controller 를 present 시키는 방식으로 연동할 수 있다고 합니다. 우리가 이미 익숙한 방식으로 Controller 를 인스턴스화하고 present 시켜주면 뭔가 될 것 같아요! 벌써부터 별로 어려워보이지 않죠? ㅎㅎㅎ 

 

 

그리고 아래쪽을 좀 더 보면 이렇게 2가지 Controller 가 제공된다는 것을 확인할 수 있습니다. 딱 이름만 봐도 어느 컨트롤러를 써야할지 감이 오네요. 우리는 오늘 Email 을 보내기 기능을 구현하는 것이 목표니까 SMS 에 관한 부분은 스킵하고 MFMailComposeViewController 만 알아볼거에요.

 

 

이유는 모르겠지만 UINavigationController 를 상속받아 만든 Controller 인가 봅니다. 왜 굳이 이걸 상속받았을까요...

 

아무튼!! 이 컨트롤러를 사용하면 사용자가 메일을 보낼 수 있도록 도와주는 창을 띄울 수 있고, 이 창으로 우리가 갖고 있는 컨텐츠(보낼 사람, 제목, 내용, 첨부파일 등)를 포함한채로 생성되도록 만들 수 있다고 합니다. 그럼 이제 직접 한번 구현해볼게요.


사용자의 디바이스에서 이메일이 사용 가능한지 확인하기

가장 먼저 사용자의 디바이스에서 이메일이 사용 가능한 상태인지 확인해야 합니다. 사용할 수 없는 상황은 기본 Apple 이메일 앱을 삭제했거나 앱이 깔려는 있지만 한번도 사용하지 않아 로그인이 되지 않은 경우 등등이 있는 것 같아요. 당연히 더 많은 케이스들이 있을 수 있겠죠?

 

이메일의 작동여부를 확인하기 위해 저는 TextView 하나와 Button 하나를 배치했어요. 아래 같은 느낌으로 ㅋㅋㅋ

 

 

일단 MessageUI 를 import 해주세요.

 

import MessageUI

 

그리고나서 현재 디바이스에서 이메일 보내기 기능을 사용할 수 있는지 확인하는 함수를 하나 만들어 보겠습니다. 아래 코드를 작성하고 원하는 타이밍에 함수를 실행해 Availability 를 체크해주면 됩니다.

 

private func checkEmailAvailability() {
    if !MFMailComposeViewController.canSendMail() {
        print("Mail services are not available")
        return
    }
}

 

이번 실습에서는 viewDidLoad() 에서 최초에 한번 체크할 수 있도록 만들어볼게요.

 

override func viewDidLoad() {
    super.viewDidLoad()
    checkEmailAvailability()
}

 

가장 쉽게 이 메세지를 확인하는 방법은 시뮬레이터를 실행하면 됩니다. 시뮬레이터는 이메일 기능을 지원하지 않기 때문에 바로 이 함수가 작동되거든요 ㅎㅎ 그럼 한번 실행을 해보면~ 이렇게 메일 서비스를 사용할 수 없다고 나옵니다.

 

시뮬레이터가 아니더라도 메일이 정상적으로 동작하지 못하는 상황이 있을 수 있으니 이 함수를 사용해서 먼저 확인을 하는 것이 안전합니다. 이게 확인 없이 실행할 수 없는 기기에서 MFMailComposeViewController 에 접근하려고 시도하면 크래시가 발생합니다. 그러니까 꼭 확인하고 사용할 수 없을 경우 접근하지 않도록 설정해줘야겠죠!

 


MFMailComposeViewController 생성 및 설정하기

이제부터는 실제로 이메일을 보낼 수 있는 환경에서 작업해야 코드가 정상적으로 작동하는지 확인할 수 있으니까 시뮬레이터가 아닌 아이폰에서 앱을 구동해볼게요. 이메일을 보낼 수 있는 컨트롤러를 띄우기 위해 위에서 잠깐 살펴봤던 MFMailComposeViewController 를 인스턴스화 합니다.

 

let composeVC = MFMailComposeViewController()

 

이제 컨트롤러를 띄울 때 들고갈 정보를 세팅하도록 할게요. setTopRecipients 는 보내는 사람, setSubject 는 이메일 제목, setMessageBody 에 이메일 내용을 입력할 수 있습니다. 당연히 비워두거나 작성하지 않아도되고 이러면 빈 이메일 작성 폼으로 시작하게 됩니다.

 

이메일이 잘 발송되는지 확인해보려면 setToRecipients 부분에는 본인의 이메일 주소를 넣어주세요 ㅎㅎ 그리고 원래는 만들어둔 TextView 의 값을 이메일 내용으로 받으려고 했는데 하다보니 그럴 필요까지 없을 것 같아서 굳이 그렇게 하지 않을게요 ㅋㅋㅋ 혹시 이렇게 하고싶은 분들은 TextView 의 Text 값을 setMessageBody 값으로 넣어주면 됩니다~

 

버튼에 함수를 연결하고 그 함수 내부에 하고 아래 코드를 입력해 볼게요.

 

composeVC.setToRecipients(["myEmail@gmail.com"])
composeVC.setSubject("Message Subject")
composeVC.setMessageBody("Some Message", isHTML: false)

self.present(composeVC, animated: true, completion: nil)

 

자! 아이폰에서 앱을 실행하고 버튼을 눌러보면 이메일 창이 열립니다!! 사용자가 추가적으로 내용을 편집하거나 작성한 뒤에 이메일을 보낼 수도 있습니다. 

 

 

그런데 아직 문제가 하나 있는데요. Cancel 버튼을 눌러보면 ActionAlert 가 올라오며 아래와 같은 화면이 뜨는데 어떤 선택을 하든 ComposeVC 가 dismiss 되지 않고 그대로 남아있습니다. 그러니까 우리는 Cancel 버튼이 눌렸을 때 뷰가 dismiss 될 수 있도록 추가적인 작업을 해야합니다.

 


MFMailComposeViewControllerDelegate 채택하기

ViewController 에 MFMailComposeViewControllerDelegate 를 채택하고 mailComposeController(didFinishWith:) 함수를 구현해주세요. 이 함수는 이메일 보내기 행동이 종료되었을 때 어떻게 동작할지 결정해주는 함수입니다. 우리는 View 가 Dismiss 되도록 코드를 작성할거에요.

 

extension ViewController: MFMailComposeViewControllerDelegate {
    
    func mailComposeController(_ controller: MFMailComposeViewController,
                               didFinishWith result: MFMailComposeResult, error: Error?) {
        dismiss(animated: true, completion: nil)
    }
    
}

 

Delegate 위임하는 것도 잊으면 안되겠죠? 저는 버튼과 연결해놓은 함수에 코드를 작성했어요.

 

composeVC.mailComposeDelegate = self

 

이제 다시 앱을 실행시키고 Cancel 버튼을 누르면 우리가 원하는대로 정상적으로 Dismiss 가 되는 것을 확인할 수 있습니다.

 

 

이렇게 Apple 에서 제공하는 Controller 를 통해 이메일을 보낼 수 있도록 구현하는 방법을 알아보았습니다. 혹시 필요한 분들을 위해 전체 코드도 남겨드릴테니 참고하세요.


Entire Code

import UIKit
import MessageUI

class ViewController: UIViewController {
    
    // MARK: - Properties
    let textView = UITextView()
    let button = UIButton(type: .system)
    let composeVC = MFMailComposeViewController()
    
    // MARK: - Lifecycle
    override func viewDidLoad() {
        super.viewDidLoad()
        configureUI()
        checkEmailAvailability()
    }
    
    // MARK: - UI
    private func configureUI() {
        setPropertyAttributes()
        setConstraints()
    }
    
    private func setPropertyAttributes() {
        textView.backgroundColor = .lightGray
        button.setTitle("보내기", for: .normal)
        button.addTarget(self, action: #selector(handleButton(_:)), for: .touchUpInside)
    }
    
    private func setConstraints() {
        view.addSubview(textView)
        view.addSubview(button)
        textView.translatesAutoresizingMaskIntoConstraints = false
        button.translatesAutoresizingMaskIntoConstraints = false
        
        NSLayoutConstraint.activate([
            textView.centerXAnchor.constraint(equalTo: view.centerXAnchor),
            textView.centerYAnchor.constraint(equalTo: view.centerYAnchor, constant: -150),
            textView.widthAnchor.constraint(equalToConstant: 300),
            textView.heightAnchor.constraint(equalToConstant: 300),
            
            button.topAnchor.constraint(equalTo: textView.bottomAnchor, constant: 10),
            button.centerXAnchor.constraint(equalTo: view.centerXAnchor),
        ])
    }
    
    // MARK: - Helpers
    private func checkEmailAvailability() {
        if !MFMailComposeViewController.canSendMail() {
            print("Mail services are not available")
            return
        }
    }
    
    // MARK: - Selectors
    @objc
    private func handleButton(_ sender: UIButton) {
        composeVC.mailComposeDelegate = self
        // Configure the fields of the interface.
        composeVC.setToRecipients(["myEmail@gmail.com"])
        composeVC.setSubject("Message Subject")
        composeVC.setMessageBody("Some Message", isHTML: false)
        // Present the view controller modally.
        self.present(composeVC, animated: true, completion: nil)
    }
    
}

// MARK: - MFMailComposeViewControllerDelegate
extension ViewController: MFMailComposeViewControllerDelegate {
    func mailComposeController(_ controller: MFMailComposeViewController,
                               didFinishWith result: MFMailComposeResult, error: Error?) {
        dismiss(animated: true, completion: nil)
    }
}