Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add example of using editorInputAccessoryView to Example SwiftUI #6

Open
ChaseHaymond opened this issue Oct 3, 2024 · 3 comments
Open
Labels
documentation Improvements or additions to documentation

Comments

@ChaseHaymond
Copy link

ChaseHaymond commented Oct 3, 2024

I have been trying to get editorInputAccessoryView to work with my SwiftUI code for an app on iOS. I can't get the buttons in the UIView I create to work. Can you add an example to 'Example SwiftUI' that uses editorInputAccessoryView?

I tried using similar code to what 'Example iOS' uses in setupToolbar(). I was able to get the buttons to appear in the toolbar, but I couldn't get them to do anything when pressed.

I'm not sure what I am doing wrong, and I would love if the example code showed a way to use editorInputAccessoryView with SwiftUI.

@ChaseHaymond
Copy link
Author

A little update. I got the buttons to recognize being tapped. But a lot of the TextAttributes functionality doesn't seem to work right. Below is code that will add just a bold button. When I select text and press bold it is very jittery and doesn't bold the text. It will cause any new text I write to be bolded.

import SwiftUI
import InfomaniakRichHTMLEditor

struct ContentView: View {

    @ObservedObject var agent: ContentAgent = ContentAgent()
    @State private var htmlString: String = ""

    var body: some View {
        List {

            RichHTMLEditor(html: $htmlString, textAttributes: agent.textAttributes)
                .editorInputAccessoryView(agent.toolbarView)

            //Text(htmlString)
        }
    }
}

This is based on the code found in Example iOS, but made to work better with SwiftUI.

import Foundation
import UIKit
import InfomaniakRichHTMLEditor

class ContentAgent: ObservableObject {

    @Published var toolbarView: UIView
    @Published var textAttributes: TextAttributes
    private var toolbarButtons: [UIView]

    init() {
        self.textAttributes = TextAttributes()
        self.toolbarButtons = [UIView]()
        self.toolbarView = UIView()

        self.toolbarView = setupToolbar()
    }

    private func setupToolbar() -> UIView {
        let view = UIView(frame: CGRect(x: 0, y: 0, width: 200, height: 56))
        view.backgroundColor = .systemGray6

        let scrollView = setupScrollView(to: view)
        setupAllButtons()
        setupStackView(to: scrollView)

        return view
    }

    private func setupScrollView(to view: UIView) -> UIScrollView {
        let scrollView = UIScrollView()
        scrollView.showsHorizontalScrollIndicator = false
        scrollView.translatesAutoresizingMaskIntoConstraints = false
        view.addSubview(scrollView)

        NSLayoutConstraint.activate([
            scrollView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
            scrollView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
            scrollView.topAnchor.constraint(equalTo: view.topAnchor),
            scrollView.bottomAnchor.constraint(equalTo: view.bottomAnchor)
        ])

        return scrollView
    }

    private func setupAllButtons() {
        for group in ToolbarAction.actionGroups {
            for action in group {
                let button = createButton(action: action)
                button.addTarget(self, action: #selector(didTapToolbarButton), for: .touchUpInside)
                toolbarButtons.append(button)
            }

            if group != ToolbarAction.actionGroups.last {
                let divider = createDivider()
                toolbarButtons.append(divider)
            }
        }
    }

    private func createButton(action: ToolbarAction) -> UIButton {
        let button = UIButton(configuration: .borderless())
        button.setImage(action.icon, for: .normal)
        button.tag = action.rawValue
        button.translatesAutoresizingMaskIntoConstraints = false

        NSLayoutConstraint.activate([
            button.heightAnchor.constraint(equalToConstant: 40),
            button.widthAnchor.constraint(equalToConstant: 40)
        ])
        return button
    }

    private func createDivider() -> UIDivider {
        let divider = UIDivider()
        divider.translatesAutoresizingMaskIntoConstraints = false

        NSLayoutConstraint.activate([
            divider.widthAnchor.constraint(equalToConstant: 1),
            divider.heightAnchor.constraint(equalToConstant: 30)
        ])
        return divider
    }

    private func setupStackView(to scrollView: UIScrollView) {
        let stackView = UIStackView(arrangedSubviews: toolbarButtons)
        stackView.translatesAutoresizingMaskIntoConstraints = false
        stackView.axis = .horizontal
        stackView.alignment = .center
        stackView.spacing = 8
        stackView.layoutMargins = UIEdgeInsets(top: 0, left: 8, bottom: 0, right: 8)
        stackView.isLayoutMarginsRelativeArrangement = true
        scrollView.addSubview(stackView)

        NSLayoutConstraint.activate([
            stackView.leadingAnchor.constraint(equalTo: scrollView.contentLayoutGuide.leadingAnchor),
            stackView.trailingAnchor.constraint(equalTo: scrollView.contentLayoutGuide.trailingAnchor),
            stackView.topAnchor.constraint(equalTo: scrollView.contentLayoutGuide.topAnchor),
            stackView.bottomAnchor.constraint(equalTo: scrollView.contentLayoutGuide.bottomAnchor),

            scrollView.frameLayoutGuide.heightAnchor.constraint(equalTo: stackView.heightAnchor)
        ])
    }
}

extension ContentAgent {
    @objc func didTapToolbarButton(_ sender: UIButton) {
        guard let action = ToolbarAction(rawValue: sender.tag) else {
            return
        }

        switch action {
        case .bold:
            textAttributes.bold()
        }
    }
}

final class UIDivider: UIView {
    init() {
        super.init(frame: .zero)
        backgroundColor = .systemGray4
    }

    @available(*, unavailable)
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
}

This is a modified version of the ToolbarAction found in Example iOS. this one only contains bold.

import InfomaniakRichHTMLEditor
import UIKit

enum ToolbarAction: Int {
    case dismissKeyboard, bold, italic, underline, strikethrough, link, toggleSubscript, toggleSuperscript, orderedList,
         unorderedList, justifyFull, justifyLeft, justifyCenter, justifyRight, fontName, fontSize, foregroundColor,
         backgroundColor, outdent, indent, undo, redo, removeFormat

    static let actionGroups: [[Self]] = [
        [.dismissKeyboard],
        [.bold, .italic, .underline, .strikethrough],
        [.link],
        [.toggleSubscript, .toggleSuperscript],
        [.orderedList, .unorderedList],
        [.justifyFull, .justifyLeft, .justifyCenter, .justifyRight],
        [.fontName, .fontSize],
        [.foregroundColor, .backgroundColor],
        [.outdent, .indent],
        [.undo, .redo],
        [.removeFormat]
    ]

    var icon: UIImage? {
        let systemName = switch self {
        case .bold:
            "bold"
        }

        return UIImage(systemName: systemName)
    }

    func isSelected(_ textAttributes: TextAttributes) -> Bool {
        switch self {
        case .bold:
            return textAttributes.hasBold
        }
    }
}

If you see what i am doing wrong please let me know.

@valentinperignon
Copy link
Member

Hi @ChaseHaymond,

You are right, using an Input Accessory View with SwiftUI can be a bit tricky.
We should add an example in the "Example SwiftUI".

You can see what I've done for Infomaniak Mail :

So far for your code, this is what I can say about it:
I would not put the TextAttributes as a property of an ObservableObject.
TextAttributes is itself an ObservableObject and nested ObservableObjects are generally a bad idea.

@valentinperignon valentinperignon added the documentation Improvements or additions to documentation label Oct 7, 2024
@valentinperignon valentinperignon linked a pull request Oct 8, 2024 that will close this issue
@srosenbaumCB
Copy link

@valentinperignon Is there any update on updating the editorInputAccessoryView in the SwiftUI Example?

We are seriously considering adopting swift-rich-html-editor in place of our current usage of Aztec (which the Aztec team does not seem to want to update).

We have gotten the editor basically working but adding formatting tool buttons has been confusing. The links you listed from the Infomaniak Mail repo are 404. And trying to separate the essence to just adding a simple tool button from the rest of the KMail app repo code is a bit fuzzy because it seems to be a bit UIKit-centric.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
documentation Improvements or additions to documentation
Projects
None yet
Development

No branches or pull requests

3 participants