aboutsummaryrefslogtreecommitdiff
path: root/iosApp/iosApp/Shared/HyperlinkText.swift
blob: 4b966ebb45d106abf6e98227c896ac9dca41448d (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
// Source: https://swiftuirecipes.com/blog/hyperlinks-in-swiftui-text
import SwiftUI
import SwiftUIFlowLayout

struct HyperlinkText: View {
    private let pairs: [StringWithAttributes]
    
    init(_ attributedString: NSAttributedString) {
        pairs = attributedString.stringsWithAttributes
    }
    
    init?(html: String) {
        if let data = html.data(using: .utf8),
           let attributedString = try? NSAttributedString(data: data,
                                                          options: [.documentType: NSAttributedString.DocumentType.html],
                                                          documentAttributes: nil) {
            self.init(attributedString)
        } else {
            return nil
        }
    }
    
    var body: some View {
        FlowLayout(mode: .vstack,
                   binding: .constant(false),
                   items: pairs,
                   itemSpacing: 0) { pair in
            if let link = pair.attrs[.link],
               let url = link as? URL {
                Text(pair)
                    .onTapGesture {
                        if UIApplication.shared.canOpenURL(url) {
                            UIApplication.shared.open(url)
                        }
                    }
            } else {
                Text(pair)
            }
        }
    }
}

struct StringWithAttributes: Hashable, Identifiable {
    let id = UUID()
    let string: String
    let attrs: [NSAttributedString.Key: Any]
    
    static func == (lhs: StringWithAttributes, rhs: StringWithAttributes) -> Bool {
        lhs.id == rhs.id
    }
    
    func hash(into hasher: inout Hasher) {
        hasher.combine(id)
    }
}

extension NSAttributedString {
    var stringsWithAttributes: [StringWithAttributes] {
        var attributes = [StringWithAttributes]()
         enumerateAttributes(in: NSRange(location: 0, length: length), options: []) { (attrs, range, _) in
             let string = attributedSubstring(from: range).string
            attributes.append(StringWithAttributes(string: string, attrs: attrs))
         }
        return attributes
    }
}

extension Text {
    init(_ singleAttribute: StringWithAttributes) {
        let string = singleAttribute.string
        let attrs = singleAttribute.attrs
        var text = Text(string)
        
        /*if let font = attrs[.font] as? UIFont {
            text = text.font(.init(font))
        }
        
        if let color = attrs[.foregroundColor] as? UIColor {
            text = text.foregroundColor(Color(color))
        }*/
        
        if let kern = attrs[.kern] as? CGFloat {
            text = text.kerning(kern)
        }
        
        if #available(iOS 14.0, *) {
            if let tracking = attrs[.tracking] as? CGFloat {
                text = text.tracking(tracking)
            }
        }
        
        if let strikethroughStyle = attrs[.strikethroughStyle] as? NSNumber, strikethroughStyle != 0 {
            if let strikethroughColor = (attrs[.strikethroughColor] as? UIColor) {
                text = text.strikethrough(true, color: Color(strikethroughColor))
            } else {
                text = text.strikethrough(true)
            }
        }
        
        if let underlineStyle = attrs[.underlineStyle] as? NSNumber,
           underlineStyle != 0 {
            if let underlineColor = (attrs[.underlineColor] as? UIColor) {
                text = text.underline(true, color: Color(underlineColor))
            } else {
                text = text.underline(true)
            }
        }
        
        if let baselineOffset = attrs[.baselineOffset] as? NSNumber {
            text = text.baselineOffset(CGFloat(baselineOffset.floatValue))
        }
        
        self = text
    }
    
    init(_ attributes: [StringWithAttributes]) {
        self.init("")
        for singleAttribute in attributes {
            self = self + Text(singleAttribute)
        }
    }
    
    init(_ attributedString: NSAttributedString) {
        self.init(attributedString.stringsWithAttributes)
    }
}