Skip to content

Instantly share code, notes, and snippets.

@lis186
Created September 14, 2022 08:27
Show Gist options
  • Save lis186/286be52904b4f43408573b8ed8a319cd to your computer and use it in GitHub Desktop.
Save lis186/286be52904b4f43408573b8ed8a319cd to your computer and use it in GitHub Desktop.
Use MFMessageComposeViewController with the Composable Architecture
//
// ContentView.swift
// TextComposerViewApp
//
// Created by Yi-Hsiu Lee on 2022/9/14.
//
import ComposableArchitecture
import SwiftUI
public struct ContentEnvironment {}
public struct ContentState: Equatable {
public var textMessageComposer: TextMessageComposerState
public init(textMessageComposer: TextMessageComposerState = .init()) {
self.textMessageComposer = textMessageComposer
}
}
public enum ContentAction: Equatable {
case textMessageComposer(TextMessageComposerAction)
public var textMessageComposer: TextMessageComposerAction? {
get {
guard case .textMessageComposer(let value) = self else { return nil }
return value
}
set {
guard case .textMessageComposer = self, let newValue = newValue else { return }
self = .textMessageComposer(newValue)
}
}
}
public let contentReducer = Reducer<ContentState, ContentAction, ContentEnvironment>.combine(
textMessageComposerReducer.pullback(
state: \ContentState.textMessageComposer,
action: /ContentAction.textMessageComposer,
environment: { _ in
TextMessageComposerEnvironment(mainQueue: DispatchQueue.main.eraseToAnyScheduler())
}
),
Reducer { _, action, _ in
switch action {
case .textMessageComposer(let textMessageComposerAction):
switch textMessageComposerAction {
case .setRreceipients(let rreceipients):
return .none
case .setMessageBody(let messageBody):
return .none
case .setErrorMessage(let errorMessage):
return .none
case .showSheet:
return .none
case .onMessageComposeFinished(let messageComposeResult):
return .none
case .dismissSheet:
return .none
}
}
}
)
.debug()
struct ContentView: View {
let store: Store<ContentState, ContentAction>
var body: some View {
WithViewStore(self.store) { viewStore in
ZStack {
Button("Open TextMessageComposer") {
viewStore.send(
.textMessageComposer(
.showSheet(
.init(receipients: [],
messageBody: "Hello!")
)
)
)
}
}
.ignoresSafeArea(.all)
.sheet(
isPresented: viewStore.binding(
get: \.textMessageComposer.showingTextMessageComposer,
send: .textMessageComposer(.dismissSheet)
)
) {
TextMessageComposerView(
viewStore: .init(
self.store.scope(
state: \ContentState.textMessageComposer,
action: ContentAction.textMessageComposer
)
)
)
.task {
print("task")
}.onAppear {
print("onAppear")
}
}
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView(
store: Store(
initialState: .init(),
reducer: contentReducer,
environment: .init()
)
)
}
}
//
// ErrorView.swift
//
//
//// Created by Yi-Hsiu Lee on 2022/9/13.
//
import Foundation
import SwiftUI
public struct ErrorView: View {
public var errorMessage: String
public var body: some View {
Text(errorMessage)
}
public init(errorMessage: String) {
self.errorMessage = errorMessage
}
}
//
// TextComposerViewAppApp.swift
// TextComposerViewApp
//
// Created by Yi-Hsiu Lee on 2022/9/14.
//
import ComposableArchitecture
import SwiftUI
@main
struct TextComposerViewAppApp: App {
var body: some Scene {
WindowGroup {
ContentView(
store: Store(
initialState: .init(),
reducer: contentReducer,
environment: .init()
)
)
}
}
}
////
//// TextMessageComposerView.swift
////
////
//// Created by Yi-Hsiu Lee on 2022/9/13.
////
//
import ComposableArchitecture
import Foundation
import MessageUI
import SwiftUI
public struct TextMessageComposerView: UIViewControllerRepresentable {
public let viewStore: ViewStore<TextMessageComposerState, TextMessageComposerAction>
public func makeUIViewController(context: UIViewControllerRepresentableContext<TextMessageComposerView>) -> UIViewController {
guard MFMessageComposeViewController.canSendText() else {
let errorView = ErrorView(errorMessage: context.coordinator.errorMessage)
let vc = UIHostingController(rootView: errorView)
return vc
}
let msgCompose = MFMessageComposeViewController()
msgCompose.messageComposeDelegate = context.coordinator
msgCompose.recipients = context.coordinator.receipients
msgCompose.body = context.coordinator.messageBody
return msgCompose
}
public func updateUIViewController(_ uiViewController: UIViewController, context: UIViewControllerRepresentableContext<TextMessageComposerView>) {}
public func makeCoordinator() -> Coordinator {
Coordinator(
self,
receipients: viewStore.binding(get: \.receipients, send: TextMessageComposerAction.setRreceipients),
messageBody: viewStore.binding(get: \.messageBody, send: TextMessageComposerAction.setMessageBody),
errorMessage: viewStore.binding(get: \.errorMessage, send: TextMessageComposerAction.setErrorMessage)
)
}
public class Coordinator: NSObject, MFMessageComposeViewControllerDelegate {
let parent: TextMessageComposerView
@Binding var receipients: [String]
@Binding var messageBody: String
@Binding var errorMessage: String
public init(_ parent: TextMessageComposerView, receipients: Binding<[String]>, messageBody: Binding<String>, errorMessage: Binding<String>) {
self.parent = parent
self._receipients = receipients
self._messageBody = messageBody
self._errorMessage = errorMessage
}
public func messageComposeViewController(_ controller: MFMessageComposeViewController, didFinishWith result: MessageComposeResult) {
parent.viewStore.send(.onMessageComposeFinished(result))
parent.viewStore.send(.dismissSheet)
}
}
public init(viewStore: ViewStore<TextMessageComposerState, TextMessageComposerAction>) {
self.viewStore = viewStore
}
}
public struct TextMessageComposerState: Equatable {
public var showingTextMessageComposer: Bool
public var receipients: [String]
public var messageBody: String
public var errorMessage: String
public init(showingTextMessageComposer: Bool = false, receipients: [String] = [], messageBody: String = "", errorMessage: String = "Cannot send message") {
self.showingTextMessageComposer = showingTextMessageComposer
self.receipients = receipients
self.messageBody = messageBody
self.errorMessage = errorMessage
}
}
public enum TextMessageComposerAction: Equatable {
case setRreceipients([String])
case setMessageBody(String)
case setErrorMessage(String)
case showSheet(SheetConfiuration)
case onMessageComposeFinished(MessageComposeResult)
case dismissSheet
}
public struct SheetConfiuration: Equatable {
public var receipients: [String]
public var messageBody: String
public init(receipients: [String], messageBody: String) {
self.receipients = receipients
self.messageBody = messageBody
}
}
public struct TextMessageComposerEnvironment {
public var mainQueue: AnySchedulerOf<DispatchQueue>
public static let live = Self(
mainQueue: DispatchQueue.main.eraseToAnyScheduler()
)
public static let test = Self(
mainQueue: DispatchQueue.main.eraseToAnyScheduler()
)
public init(mainQueue: AnySchedulerOf<DispatchQueue>) {
self.mainQueue = mainQueue
}
}
public let textMessageComposerReducer = Reducer<TextMessageComposerState, TextMessageComposerAction, TextMessageComposerEnvironment> { state, action, _ in
switch action {
case .setRreceipients(let receipients):
state.receipients = receipients
return .none
case .setMessageBody(let messageBody):
state.messageBody = messageBody
return .none
case .setErrorMessage(let errorMessage):
state.errorMessage = errorMessage
return .none
case .showSheet(let sheetConfiuration):
state.receipients = sheetConfiuration.receipients
state.messageBody = sheetConfiuration.messageBody
state.showingTextMessageComposer = true
return .none
case .onMessageComposeFinished(let result):
print(result)
return .none
case .dismissSheet:
state.showingTextMessageComposer = false
return .none
}
}
.debug()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment