diff --git a/Sources/WysiwygComposer/Components/WysiwygComposerView/WysiwygComposerView.swift b/Sources/WysiwygComposer/Components/WysiwygComposerView/WysiwygComposerView.swift index 1af9090..cef6156 100644 --- a/Sources/WysiwygComposer/Components/WysiwygComposerView/WysiwygComposerView.swift +++ b/Sources/WysiwygComposer/Components/WysiwygComposerView/WysiwygComposerView.swift @@ -27,8 +27,6 @@ public protocol WysiwygItemProviderHelper { func isPasteSupported(for itemProvider: NSItemProvider) -> Bool } -/// Handler for key commands. -public typealias KeyCommandHandler = (WysiwygKeyCommand) -> Bool /// Handler for paste events. public typealias PasteHandler = (NSItemProvider) -> Void @@ -43,7 +41,7 @@ public struct WysiwygComposerView: View { private let placeholderColor: Color private let viewModel: WysiwygComposerViewModelProtocol private let itemProviderHelper: WysiwygItemProviderHelper? - private let keyCommandHandler: KeyCommandHandler? + private let keyCommands: [WysiwygKeyCommand]? private let pasteHandler: PasteHandler? // MARK: - Public @@ -57,28 +55,27 @@ public struct WysiwygComposerView: View { /// See `WysiwygComposerViewModel.swift` for triggerable actions. /// - itemProviderHelper: A helper to determine if an item can be pasted into the hosting application. /// If omitted, most non-text paste events will be ignored. - /// - keyCommandHandler: A handler for key commands. - /// If omitted, default behaviour will be applied. See `WysiwygKeyCommand.swift`. + /// - keyCommands: The supported key commands that can be provided with their associated action /// - pasteHandler: A handler for paste events. /// If omitted, the composer will try to paste content as raw text. public init(placeholder: String, placeholderColor: Color = .init(UIColor.placeholderText), viewModel: WysiwygComposerViewModelProtocol, itemProviderHelper: WysiwygItemProviderHelper?, - keyCommandHandler: KeyCommandHandler?, + keyCommands: [WysiwygKeyCommand]?, pasteHandler: PasteHandler?) { self.placeholder = placeholder self.placeholderColor = placeholderColor self.viewModel = viewModel self.itemProviderHelper = itemProviderHelper - self.keyCommandHandler = keyCommandHandler + self.keyCommands = keyCommands self.pasteHandler = pasteHandler } public var body: some View { UITextViewWrapper(viewModel: viewModel, itemProviderHelper: itemProviderHelper, - keyCommandHandler: keyCommandHandler, + keyCommands: keyCommands, pasteHandler: pasteHandler) .accessibilityLabel(placeholder) .background(placeholderView, alignment: .topLeading) @@ -105,7 +102,7 @@ struct UITextViewWrapper: UIViewRepresentable { /// If omitted, most non-text paste events will be ignored. private let itemProviderHelper: WysiwygItemProviderHelper? /// A handler for key commands. If omitted, default behaviour will be applied. See `WysiwygKeyCommand.swift`. - private let keyCommandHandler: KeyCommandHandler? + private let keyCommands: [WysiwygKeyCommand]? /// A handler for paste events. If omitted, the composer will try to paste content as raw text. private let pasteHandler: PasteHandler? @@ -115,10 +112,10 @@ struct UITextViewWrapper: UIViewRepresentable { init(viewModel: WysiwygComposerViewModelProtocol, itemProviderHelper: WysiwygItemProviderHelper?, - keyCommandHandler: KeyCommandHandler?, + keyCommands: [WysiwygKeyCommand]?, pasteHandler: PasteHandler?) { self.itemProviderHelper = itemProviderHelper - self.keyCommandHandler = keyCommandHandler + self.keyCommands = keyCommands self.pasteHandler = pasteHandler self.viewModel = viewModel } @@ -151,9 +148,8 @@ struct UITextViewWrapper: UIViewRepresentable { Coordinator(viewModel.replaceText, viewModel.select, viewModel.didUpdateText, - viewModel.enter, itemProviderHelper: itemProviderHelper, - keyCommandHandler: keyCommandHandler, + keyCommands: keyCommands, pasteHandler: pasteHandler) } @@ -167,25 +163,22 @@ struct UITextViewWrapper: UIViewRepresentable { var replaceText: (NSRange, String) -> Bool var select: (NSRange) -> Void var didUpdateText: () -> Void - var enter: () -> Void + let keyCommands: [WysiwygKeyCommand]? private let itemProviderHelper: WysiwygItemProviderHelper? - private let keyCommandHandler: KeyCommandHandler? private let pasteHandler: PasteHandler? init(_ replaceText: @escaping (NSRange, String) -> Bool, _ select: @escaping (NSRange) -> Void, _ didUpdateText: @escaping () -> Void, - _ enter: @escaping () -> Void, itemProviderHelper: WysiwygItemProviderHelper?, - keyCommandHandler: KeyCommandHandler?, + keyCommands: [WysiwygKeyCommand]?, pasteHandler: PasteHandler?) { self.replaceText = replaceText self.select = select self.didUpdateText = didUpdateText - self.enter = enter self.itemProviderHelper = itemProviderHelper - self.keyCommandHandler = keyCommandHandler + self.keyCommands = keyCommands self.pasteHandler = pasteHandler } @@ -231,32 +224,13 @@ struct UITextViewWrapper: UIViewRepresentable { guard let itemProviderHelper else { return false } - + return itemProviderHelper.isPasteSupported(for: itemProvider) } - func textViewDidReceiveKeyCommand(_ textView: UITextView, keyCommand: WysiwygKeyCommand) { - if !handleKeyCommand(keyCommand) { - processDefault(for: keyCommand) - } - } - func textView(_ textView: UITextView, didReceivePasteWith provider: NSItemProvider) { pasteHandler?(provider) } - - private func handleKeyCommand(_ keyCommand: WysiwygKeyCommand) -> Bool { - guard let keyCommandHandler else { return false } - - return keyCommandHandler(keyCommand) - } - - private func processDefault(for keyCommand: WysiwygKeyCommand) { - switch keyCommand { - case .enter, .shiftEnter: - enter() - } - } } } diff --git a/Sources/WysiwygComposer/Components/WysiwygComposerView/WysiwygTextView.swift b/Sources/WysiwygComposer/Components/WysiwygComposerView/WysiwygTextView.swift index 3c660d8..8722e05 100644 --- a/Sources/WysiwygComposer/Components/WysiwygComposerView/WysiwygTextView.swift +++ b/Sources/WysiwygComposer/Components/WysiwygComposerView/WysiwygTextView.swift @@ -24,20 +24,16 @@ protocol WysiwygTextViewDelegate: AnyObject { /// - Parameter itemProvider: The item provider. /// - Returns: True if it can be pasted, false otherwise. func isPasteSupported(for itemProvider: NSItemProvider) -> Bool - - /// Notify the delegate that a key command has been received by the text view. - /// - /// - Parameters: - /// - textView: Composer text view. - /// - keyCommand: Key command received. - func textViewDidReceiveKeyCommand(_ textView: UITextView, keyCommand: WysiwygKeyCommand) - + /// Notify the delegate that a paste event has beeb received by the text view. /// /// - Parameters: /// - textView: Composer text view. /// - provider: Item provider for the paste event. func textView(_ textView: UITextView, didReceivePasteWith provider: NSItemProvider) + + /// The supported key commands for the text view. + var keyCommands: [WysiwygKeyCommand]? { get } } /// A markdown protocol used to provide additional context to the text view when displaying mentions through the text attachment provider @@ -161,15 +157,14 @@ public class WysiwygTextView: UITextView { // Enter Key commands support override public var keyCommands: [UIKeyCommand]? { - WysiwygKeyCommand.allCases.map { UIKeyCommand(input: $0.input, - modifierFlags: $0.modifierFlags, - action: #selector(keyCommandAction)) } + wysiwygDelegate?.keyCommands?.map { UIKeyCommand(input: $0.input, + modifierFlags: $0.modifierFlags, + action: #selector(keyCommandAction)) } } - - @objc func keyCommandAction(sender: UIKeyCommand) { - guard let command = WysiwygKeyCommand.from(sender) else { return } - - wysiwygDelegate?.textViewDidReceiveKeyCommand(self, keyCommand: command) + + // This needs to be handled here, if the selector was directly added to the WysiwygKeyCommand it would not work properly. + @objc private func keyCommandAction(sender: UIKeyCommand) { + wysiwygDelegate?.keyCommands?.first(where: { $0.input == sender.input && $0.modifierFlags == sender.modifierFlags })?.action() } // Paste support diff --git a/Sources/WysiwygComposer/Components/WysiwygKeyCommand.swift b/Sources/WysiwygComposer/Components/WysiwygKeyCommand.swift index 6912cd7..2fe1ef1 100644 --- a/Sources/WysiwygComposer/Components/WysiwygKeyCommand.swift +++ b/Sources/WysiwygComposer/Components/WysiwygKeyCommand.swift @@ -16,33 +16,14 @@ import UIKit -/// An enum describing key commands that can be handled by the hosting application. -/// This can be done by providing a `KeyCommandHandler` to the `WysiwygComposerView`. -/// If handler is nil, or if the handler returns false, a default behaviour will be applied (see cases description). -public enum WysiwygKeyCommand: CaseIterable { - /// User pressed `enter`. Default behaviour: a line feed is created. - /// Note: in the context of a messaging app, this is usually used to send a message. - case enter - /// User pressed `shift` + `enter`. Default behaviour: a line feed is created. - case shiftEnter - - var input: String { - switch self { - case .enter, .shiftEnter: - return "\r" - } - } - - var modifierFlags: UIKeyModifierFlags { - switch self { - case .enter: - return [] - case .shiftEnter: - return .shift - } - } - - static func from(_ keyCommand: UIKeyCommand) -> WysiwygKeyCommand? { - WysiwygKeyCommand.allCases.first(where: { $0.input == keyCommand.input && $0.modifierFlags == keyCommand.modifierFlags }) +/// An class that describes key commands that can be handled by the hosting application wth their associated action +public struct WysiwygKeyCommand { + /// A default initialiser for the enter command which is most commonly used + public static func enter(action: @escaping () -> Void) -> WysiwygKeyCommand { + WysiwygKeyCommand(input: "\r", modifierFlags: [], action: action) } + + let input: String + let modifierFlags: UIKeyModifierFlags + let action: () -> Void } diff --git a/Tests/WysiwygComposerSnapshotTests/SnapshotTests.swift b/Tests/WysiwygComposerSnapshotTests/SnapshotTests.swift index ce351df..d9210c5 100644 --- a/Tests/WysiwygComposerSnapshotTests/SnapshotTests.swift +++ b/Tests/WysiwygComposerSnapshotTests/SnapshotTests.swift @@ -30,7 +30,7 @@ class SnapshotTests: XCTestCase { let composerView = WysiwygComposerView(placeholder: "Placeholder", viewModel: viewModel, itemProviderHelper: nil, - keyCommandHandler: nil, + keyCommands: nil, pasteHandler: nil) hostingController = UIHostingController(rootView: VStack { // Set the composer's text view at the top of the controller. diff --git a/WysiwygComposerFFI.xcframework/ios-arm64/WysiwygComposerFFI.framework/WysiwygComposerFFI b/WysiwygComposerFFI.xcframework/ios-arm64/WysiwygComposerFFI.framework/WysiwygComposerFFI index 41759d8..00a5441 100644 Binary files a/WysiwygComposerFFI.xcframework/ios-arm64/WysiwygComposerFFI.framework/WysiwygComposerFFI and b/WysiwygComposerFFI.xcframework/ios-arm64/WysiwygComposerFFI.framework/WysiwygComposerFFI differ diff --git a/WysiwygComposerFFI.xcframework/ios-arm64_x86_64-simulator/WysiwygComposerFFI.framework/WysiwygComposerFFI b/WysiwygComposerFFI.xcframework/ios-arm64_x86_64-simulator/WysiwygComposerFFI.framework/WysiwygComposerFFI index 971f36e..91311b6 100644 Binary files a/WysiwygComposerFFI.xcframework/ios-arm64_x86_64-simulator/WysiwygComposerFFI.framework/WysiwygComposerFFI and b/WysiwygComposerFFI.xcframework/ios-arm64_x86_64-simulator/WysiwygComposerFFI.framework/WysiwygComposerFFI differ