gualtierofrigerio / SwiftUIAttributedStrings

Sample project with NSAttributedString in SwiftUI
1 stars 0 forks source link

App is Crashing #1

Open halilyuce opened 4 years ago

halilyuce commented 4 years ago

Hey, thanks for this repo. I am using it for render html as NSAttributedString thanks to this but when I open page app is crashing. I researched and they said you must add DispatchQueue.main.async to your code for solve it. Do you have any idea about it ? I added but it is still same.

class ViewWithLabel : UIView {
    private var label = UILabel()

    override init(frame: CGRect) {
            super.init(frame:frame)
            self.addSubview(label)
            label.numberOfLines = 0
            label.autoresizingMask = [.flexibleWidth, .flexibleHeight]
    }

    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    func setString(_ attributedString:NSAttributedString) {
        DispatchQueue.main.async {
            self.label.attributedText = attributedString
            self.label.sizeThatFits(CGSize(width: UIScreen.main.bounds.width - 32, height: 9999))
            self.label.textColor = UIColor(named: "DarkColor")!
        }
    }
}
struct TextWithAttributedString: UIViewRepresentable {

    var attributedString: NSAttributedString

    func makeUIView(context: Context) -> ViewWithLabel {
        let view = ViewWithLabel(frame: .zero)
        return view
    }

    func updateUIView(_ uiView: ViewWithLabel, context: Context) {
        uiView.setString(attributedString)
    }
}
gualtierofrigerio commented 4 years ago

Does it crash with every string you pass to setString? If you have a sample string I can try to see if it crashes for me as well

halilyuce commented 4 years ago

I solved it with dispatchqueue main async but now it is not calculating true label height size sometimes, I think it is about intrinsicContentSize override is not working at the same time because of dispatch.

Full code, any suggestions ? @gualtierofrigerio

class ViewWithLabel : UIView {
    private var label = UILabel()

    override init(frame: CGRect) {
            super.init(frame:frame)
            self.addSubview(label)
            label.numberOfLines = 0
            label.autoresizingMask = [.flexibleWidth, .flexibleHeight]
    }

    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    override var intrinsicContentSize: CGSize{
        self.label.sizeThatFits(CGSize(width: UIScreen.main.bounds.width - 32, height: 9999))
    }

    func setString(_ html:String) {
            DispatchQueue.main.async {
                self.label.attributedText = html.convertHtml()
                self.label.textColor = UIColor(named: "DarkColor")!
            }
    }
}

struct TextWithAttributedString: UIViewRepresentable {

    var html: String

    func makeUIView(context: Context) -> ViewWithLabel {
        let view = ViewWithLabel(frame: .zero)
        return view
    }

    func updateUIView(_ uiView: ViewWithLabel, context: Context) {
        uiView.setString(html)
    }
}

Extension :

extension String {
    func convertHtml() -> NSAttributedString {
        guard let data = data(using: .unicode, allowLossyConversion: true) else { return NSAttributedString() }

        if let attributedString = try? NSMutableAttributedString(data: data, options: [.documentType: NSAttributedString.DocumentType.html], documentAttributes: nil) {
            return attributedString
        } else {
            return NSAttributedString()
        }
    }
}

NewsDetail.swift

import Foundation
import SwiftUI
import SDWebImageSwiftUI

struct NewsDetail: View {

    var title:String = ""
    var content:String = ""
    var image:String = ""
    var video:String = ""
    var imageShare = UIImageView()
    @State private var showShareSheet = false
    let screenWidth = UIScreen.main.bounds.width
    let formatString = "<span style=\"font-family: '-apple-system', 'HelveticaNeue'; font-size:17\">%@</span>"

    init(title:String,content:String,image:String,video:String){
        self.content = content
        self.title = title
        self.image = image
        self.video = video
        self.imageShare.downloaded(from: image)
    }

    var body: some View {
        ScrollView(.vertical, showsIndicators: true) {
            VStack(alignment: .leading, spacing: 20){
                Group{
                    if self.video != ""{
                        YoutubePlayer(videoID: video).frame(height:210)
                    }else {
                        WebImage(url: URL(string:image))
                            .resizable()
                            .indicator(.activity)
                            .animation(.easeInOut(duration: 0.5))
                            .transition(.fade)
                            .aspectRatio(contentMode: .fill)
                            .frame(width:screenWidth, height: 250)
                            .clipped()
                    }
                }
                Text(title).fontWeight(.bold).lineLimit(.none).padding(.horizontal).font(.title)
            }
            TextWithAttributedString(html: String(format:formatString, content)).padding(.horizontal).frame(width: screenWidth)
            Divider()
        }
        .sheet(isPresented: $showShareSheet) {
            ShareSheet(activityItems: [self.imageShare.image!, self.title])
        }
        .navigationBarTitle(Text(title), displayMode: .inline)
        .navigationBarItems(trailing:
            Button(action: {
            }, label: {
                Button(action: {
                    self.showShareSheet = true
                }) {
                    Image(systemName: "square.and.arrow.up")
                }
            })
        )
    }
}