ZhgChgLi / ZMarkupParser

ZMarkupParser is a pure-Swift library that helps you convert HTML strings into NSAttributedString with customized styles and tags.
https://zhgchg.li
MIT License
294 stars 23 forks source link

Provide better support for SwiftUI and AttributedString. #12

Open makhocheung opened 1 year ago

makhocheung commented 1 year ago

Here is the example.

import Combine
import SwiftUI
import ZMarkupParser

struct MainView: View {
    @State var content = AttributedString("None")
    var body: some View {
        ScrollView {
            VStack {
                Text(content)
            }
        }
        .onAppear {
            content = AttributedString(parser.render(html))
        }
    }
}

struct MainView_Previews: PreviewProvider {
    static var previews: some View {
        MainView()
    }
}

let parser = ZHTMLParserBuilder.initWithDefault().set(rootStyle: MarkupStyle(font: MarkupStyleFont(size: 13))).build()

let html = #"""

Powered by ZhgChgLi. <br/>

<img src="https://user-images.githubusercontent.com/33706588/219608966-20e0c017-d05c-433a-9a52-091bc0cfd403.jpg"/>

🎄🎄🎄 <Hottest> <b>Christmas gi<u>fts</b> are here</u>! Give you more gift-giving inspiration~<br />
        The <u>final <del>countdown</del></u> on 12/9, NT$100 discount for all purchases over NT$1,000, plus a 12/12 one-day limited free shipping coupon<br />
        <zhgchgli>Top 10 Popular <b><span style="color:green">Christmas</span> Gift</b> Recommendations 👉</zhgchgli><br>
        <ol>
        <li><a href="https://zhgchg.li">Christmas Mini Diffuser Gift Box</a>|The first choice for exchanging gifts</li>
        <li><a href="https://zhgchg.li">German design hair remover</a>|<strong>500</strong> yuan practical gift like this</li>
        <li><a href="https://zhgchg.li">Drink cup</a>|Fund-raising and praise exceeded 10 million</li>
        </ol>
        <hr/>
        <p>Before 12/26, place an order and draw a round-trip ticket for two to Japan!</p>
        你好你好<span style="background-color:red">你好你好</span>你好你好 <br />
        안녕하세요안녕하세<span style="color:red">요안녕하세</span>요안녕하세요안녕하세요안녕하세요 <br />
        <span style="color:red">こんにちは</span>こんにちはこんにちは <br />


<h1>不支持</h1>
<h2>不支持</h2>
<h3>不支持</h3>
<h4>不支持</h4>
<h5>不支持</h5>
<h6>不支持</h6>

"""#

image

Here is the error printed by console.

2023-03-13 16:46:54.445491+0800 Demo[41916:3803493] [Demo] CGImageDestinationCreateWithData:4044: *** ERROR: CGImageDestinationCreateWithData: invalid capacity (0)
2023-03-13 16:46:54.445856+0800 Demo[41916:3803493] [Demo] finalizeDestination:3205: *** ERROR: CGImageDestinationFinalize was called, but there were no images added
2023-03-13 16:46:54.445881+0800 Demo[41916:3803493] CGImageDestinationFinalize failed for output type 'public.tiff'
zhgchgli0718 commented 1 year ago

Hi, @makhocheung thank you for using and providing feedback.

From the content of your issue, I have identified three problems that need to be resolved:

I'll resolve it as soon as possible.

Thank you again for your feedback, I have not fully tested on macOS or using SwiftUI yet.

zhgchgli0718 commented 1 year ago

After conducting research, I have concluded that AttributedString and NSAttributedString are not small objects and do not share the same inheritance. Therefore, I believe that using NSAttributedString in AttributedString or SwiftUI is not a good idea. Instead, we should create a new mapper to map ZMarkupParser.Markup objects to SwiftUI Kit (Text/Image) components, rather than using NSAttributedString directly in AttributedString.

Unfortunately, it may not be implemented quickly.

A temporary dirty solution would be to find ZNSTextAttachment for each AttributedString and manually trigger startDownload() and handle delegate for it.

content.compactMap({ $0.attachment as? ZNSTextAttachment }).forEach { attachment in
     attachment.delegate = self
    attachment.startDownlaod()
})
// handler delegate
func zNSTextAttachment(didLoad textAttachment: ZNSTextAttachment, to: ZResizableNSTextAttachment) {
     //
    replace  textAttachment to to in AttributedString
}

I know it's very dirty, but as I mentioned earlier, it's not recommended to use it in SwiftUI currently.

zhgchgli0718 commented 1 year ago

Please let me know if there is anything I have misunderstood, as I am not very familiar with SwiftUI.

makhocheung commented 1 year ago

Currently,AttributedString supports markdown natively except ![](),so it's normal that AttributedString can't load remote images. Maybe Apple will add this feature in the future.

we should create a new mapper to map ZMarkupParser.Markup objects to SwiftUI Kit (Text/Image) components, rather than using NSAttributedString directly in AttributedString.

This is a good idea. Developers may hope ZMarkupParser supports SwiftUI.

Actually, I'm appreciate that you have added support of h1~h6. ZMarkupParser currently satisfies my project. I will give more feedback later. Thank you.

charrondev commented 10 months ago

I'll add on here that this is really more of a failing of a failing of SwiftUI's Text() than of this library. It is very simple to convert between the 2 nowadays.

let nsAttr = parser.render(html)
let attr = AttributedString(nsAttr)

The real issue is that AttributedString and Text() are missing a lot of functionality, most notably support for NSParagraphStyle which you need to do paragraph level alignment, spacing, lineSpacing, justification, etc.

For my own projects I use a UIViewRepresentable wrapping a UITextView to get the maximum flexibility.