├── .gitignore
├── .swiftpm
└── xcode
│ └── package.xcworkspace
│ ├── contents.xcworkspacedata
│ └── xcshareddata
│ └── IDEWorkspaceChecks.plist
├── LICENSE
├── Package.swift
├── README.md
└── Sources
└── SwiftUIMailView
└── SwiftUIMailView.swift
/.gitignore:
--------------------------------------------------------------------------------
1 | # Xcode
2 | #
3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore
4 |
5 | ## User settings
6 | xcuserdata/
7 |
8 | ## compatibility with Xcode 8 and earlier (ignoring not required starting Xcode 9)
9 | *.xcscmblueprint
10 | *.xccheckout
11 |
12 | ## compatibility with Xcode 3 and earlier (ignoring not required starting Xcode 4)
13 | build/
14 | DerivedData/
15 | *.moved-aside
16 | *.pbxuser
17 | !default.pbxuser
18 | *.mode1v3
19 | !default.mode1v3
20 | *.mode2v3
21 | !default.mode2v3
22 | *.perspectivev3
23 | !default.perspectivev3
24 |
25 | ## Obj-C/Swift specific
26 | *.hmap
27 |
28 | ## App packaging
29 | *.ipa
30 | *.dSYM.zip
31 | *.dSYM
32 |
33 | ## Playgrounds
34 | timeline.xctimeline
35 | playground.xcworkspace
36 |
37 | # Swift Package Manager
38 | #
39 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies.
40 | # Packages/
41 | # Package.pins
42 | # Package.resolved
43 | # *.xcodeproj
44 | #
45 | # Xcode automatically generates this directory with a .xcworkspacedata file and xcuserdata
46 | # hence it is not needed unless you have added a package configuration file to your project
47 | # .swiftpm
48 |
49 | .build/
50 |
51 | # CocoaPods
52 | #
53 | # We recommend against adding the Pods directory to your .gitignore. However
54 | # you should judge for yourself, the pros and cons are mentioned at:
55 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
56 | #
57 | # Pods/
58 | #
59 | # Add this line if you want to avoid checking in source code from the Xcode workspace
60 | # *.xcworkspace
61 |
62 | # Carthage
63 | #
64 | # Add this line if you want to avoid checking in source code from Carthage dependencies.
65 | # Carthage/Checkouts
66 |
67 | Carthage/Build/
68 |
69 | # Accio dependency management
70 | Dependencies/
71 | .accio/
72 |
73 | # fastlane
74 | #
75 | # It is recommended to not store the screenshots in the git repo.
76 | # Instead, use fastlane to re-generate the screenshots whenever they are needed.
77 | # For more information about the recommended setup visit:
78 | # https://docs.fastlane.tools/best-practices/source-control/#source-control
79 |
80 | fastlane/report.xml
81 | fastlane/Preview.html
82 | fastlane/screenshots/**/*.png
83 | fastlane/test_output
84 |
85 | # Code Injection
86 | #
87 | # After new code Injection tools there's a generated folder /iOSInjectionProject
88 | # https://github.com/johnno1962/injectionforxcode
89 |
90 | iOSInjectionProject/
91 |
--------------------------------------------------------------------------------
/.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/.swiftpm/xcode/package.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2021 Gordan Glavaš
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/Package.swift:
--------------------------------------------------------------------------------
1 | // swift-tools-version:5.3
2 | // The swift-tools-version declares the minimum version of Swift required to build this package.
3 |
4 | import PackageDescription
5 |
6 | let package = Package(
7 | name: "SwiftUIMailView",
8 | platforms: [
9 | .iOS(.v13)
10 | ],
11 | products: [
12 | // Products define the executables and libraries a package produces, and make them visible to other packages.
13 | .library(
14 | name: "SwiftUIMailView",
15 | targets: ["SwiftUIMailView"]),
16 | ],
17 | dependencies: [
18 | // Dependencies declare other packages that this package depends on.
19 | // .package(url: /* package url */, from: "1.0.0"),
20 | ],
21 | targets: [
22 | // Targets are the basic building blocks of a package. A target can define a module or a test suite.
23 | // Targets can depend on other targets in this package, and on products in packages this package depends on.
24 | .target(
25 | name: "SwiftUIMailView",
26 | dependencies: []),
27 | .testTarget(
28 | name: "SwiftUIMailViewTests",
29 | dependencies: ["SwiftUIMailView"]),
30 | ]
31 | )
32 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # SwiftUIMailView
2 |
3 | The `MailView` allows you to **send mail from SwiftUI**. You can:
4 |
5 | * Determine if you can send mail or not.
6 | * Pass subject, message and recipients to the view via a binding.
7 | * Attach files to the email.
8 | * Receive success or failure result after sending the email.
9 |
10 | The end result looks like this:
11 |
12 | 
13 |
14 | ### Recipe
15 |
16 | Check out [this recipe](https://swiftuirecipes.com/blog/send-mail-in-swiftui) for in-depth description of the component and its code. Check out [SwiftUIRecipes.com](https://swiftuirecipes.com) for more **SwiftUI recipes**!
17 |
18 | ### Sample usage
19 |
20 | ```swift
21 | struct MailViewTest: View {
22 | @State private var mailData = ComposeMailData(subject: "A subject",
23 | recipients: ["i.love@swiftuirecipes.com"],
24 | message: "Here's a message",
25 | attachments: [AttachmentData(data: "Some text".data(using: .utf8)!,
26 | mimeType: "text/plain",
27 | fileName: "text.txt")
28 | ])
29 | @State private var showMailView = false
30 |
31 | var body: some View {
32 | Button(action: {
33 | showMailView.toggle()
34 | }) {
35 | Text("Send mail")
36 | }
37 | .disabled(!MailView.canSendMail)
38 | .sheet(isPresented: $showMailView) {
39 | MailView(data: $mailData) { result in
40 | print(result)
41 | }
42 | }
43 | }
44 | }
45 | ```
46 |
47 | ### Installation
48 |
49 | This component is distrubuted as a **Swift package**.
50 |
--------------------------------------------------------------------------------
/Sources/SwiftUIMailView/SwiftUIMailView.swift:
--------------------------------------------------------------------------------
1 | import SwiftUI
2 | import UIKit
3 | import MessageUI
4 |
5 | public typealias MailViewCallback = ((Result) -> Void)?
6 |
7 | public struct MailView: UIViewControllerRepresentable {
8 | @Environment(\.presentationMode) var presentation
9 | @Binding var data: ComposeMailData
10 | let callback: MailViewCallback
11 |
12 | public init(data: Binding,
13 | callback: MailViewCallback) {
14 | _data = data
15 | self.callback = callback
16 | }
17 |
18 | public class Coordinator: NSObject, MFMailComposeViewControllerDelegate {
19 | @Binding var presentation: PresentationMode
20 | @Binding var data: ComposeMailData
21 | let callback: MailViewCallback
22 |
23 | public init(presentation: Binding,
24 | data: Binding,
25 | callback: MailViewCallback) {
26 | _presentation = presentation
27 | _data = data
28 | self.callback = callback
29 | }
30 |
31 | public func mailComposeController(_ controller: MFMailComposeViewController,
32 | didFinishWith result: MFMailComposeResult,
33 | error: Error?) {
34 | if let error = error {
35 | callback?(.failure(error))
36 | } else {
37 | callback?(.success(result))
38 | }
39 | $presentation.wrappedValue.dismiss()
40 | }
41 | }
42 |
43 | public func makeCoordinator() -> Coordinator {
44 | Coordinator(presentation: presentation, data: $data, callback: callback)
45 | }
46 |
47 | public func makeUIViewController(context: UIViewControllerRepresentableContext) -> MFMailComposeViewController {
48 | let vc = MFMailComposeViewController()
49 | vc.mailComposeDelegate = context.coordinator
50 | vc.setSubject(data.subject)
51 | vc.setToRecipients(data.recipients)
52 | vc.setMessageBody(data.message, isHTML: false)
53 | data.attachments?.forEach {
54 | vc.addAttachmentData($0.data, mimeType: $0.mimeType, fileName: $0.fileName)
55 | }
56 | vc.accessibilityElementDidLoseFocus()
57 | return vc
58 | }
59 |
60 | public func updateUIViewController(_ uiViewController: MFMailComposeViewController,
61 | context: UIViewControllerRepresentableContext) {
62 | }
63 |
64 | public static var canSendMail: Bool {
65 | MFMailComposeViewController.canSendMail()
66 | }
67 | }
68 |
69 | public struct ComposeMailData {
70 | public let subject: String
71 | public let recipients: [String]?
72 | public let message: String
73 | public let attachments: [AttachmentData]?
74 |
75 | public init(subject: String,
76 | recipients: [String]?,
77 | message: String,
78 | attachments: [AttachmentData]?) {
79 | self.subject = subject
80 | self.recipients = recipients
81 | self.message = message
82 | self.attachments = attachments
83 | }
84 |
85 | public static let empty = ComposeMailData(subject: "", recipients: nil, message: "", attachments: nil)
86 | }
87 |
88 | public struct AttachmentData {
89 | public let data: Data
90 | public let mimeType: String
91 | public let fileName: String
92 |
93 | public init(data: Data,
94 | mimeType: String,
95 | fileName: String) {
96 | self.data = data
97 | self.mimeType = mimeType
98 | self.fileName = fileName
99 | }
100 | }
101 |
102 | struct MailViewTest: View {
103 | @State private var mailData = ComposeMailData(subject: "A subject",
104 | recipients: ["i.love@swiftuirecipes.com"],
105 | message: "Here's a message",
106 | attachments: [AttachmentData(data: "Some text".data(using: .utf8)!,
107 | mimeType: "text/plain",
108 | fileName: "text.txt")
109 | ])
110 | @State private var showMailView = false
111 |
112 | var body: some View {
113 | Button(action: {
114 | showMailView.toggle()
115 | }) {
116 | Text("Send mail")
117 | }
118 | .disabled(!MailView.canSendMail)
119 | .sheet(isPresented: $showMailView) {
120 | MailView(data: $mailData) { result in
121 | print(result)
122 | }
123 | }
124 | }
125 | }
126 |
--------------------------------------------------------------------------------