Move AttributedText view into it's own file
This commit is contained in:
parent
c2c995e63c
commit
7ad4b26fc6
4 changed files with 106 additions and 101 deletions
|
@ -16,6 +16,7 @@
|
||||||
03BCD7432488947200DA1F27 /* ProfileView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03BCD7422488947200DA1F27 /* ProfileView.swift */; };
|
03BCD7432488947200DA1F27 /* ProfileView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03BCD7422488947200DA1F27 /* ProfileView.swift */; };
|
||||||
03BCD7452488948200DA1F27 /* PostView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03BCD7442488948200DA1F27 /* PostView.swift */; };
|
03BCD7452488948200DA1F27 /* PostView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03BCD7442488948200DA1F27 /* PostView.swift */; };
|
||||||
03BCD7482488985A00DA1F27 /* RemoteImage in Frameworks */ = {isa = PBXBuildFile; productRef = 03BCD7472488985A00DA1F27 /* RemoteImage */; };
|
03BCD7482488985A00DA1F27 /* RemoteImage in Frameworks */ = {isa = PBXBuildFile; productRef = 03BCD7472488985A00DA1F27 /* RemoteImage */; };
|
||||||
|
03BCD74A2489322500DA1F27 /* AttributedText.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03BCD7492489322500DA1F27 /* AttributedText.swift */; };
|
||||||
/* End PBXBuildFile section */
|
/* End PBXBuildFile section */
|
||||||
|
|
||||||
/* Begin PBXFileReference section */
|
/* Begin PBXFileReference section */
|
||||||
|
@ -29,6 +30,7 @@
|
||||||
03427F6C248887D200A0073D /* Common.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Common.swift; sourceTree = "<group>"; };
|
03427F6C248887D200A0073D /* Common.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Common.swift; sourceTree = "<group>"; };
|
||||||
03BCD7422488947200DA1F27 /* ProfileView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileView.swift; sourceTree = "<group>"; };
|
03BCD7422488947200DA1F27 /* ProfileView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileView.swift; sourceTree = "<group>"; };
|
||||||
03BCD7442488948200DA1F27 /* PostView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostView.swift; sourceTree = "<group>"; };
|
03BCD7442488948200DA1F27 /* PostView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostView.swift; sourceTree = "<group>"; };
|
||||||
|
03BCD7492489322500DA1F27 /* AttributedText.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AttributedText.swift; sourceTree = "<group>"; };
|
||||||
/* End PBXFileReference section */
|
/* End PBXFileReference section */
|
||||||
|
|
||||||
/* Begin PBXFrameworksBuildPhase section */
|
/* Begin PBXFrameworksBuildPhase section */
|
||||||
|
@ -71,6 +73,7 @@
|
||||||
03427F6C248887D200A0073D /* Common.swift */,
|
03427F6C248887D200A0073D /* Common.swift */,
|
||||||
03BCD7422488947200DA1F27 /* ProfileView.swift */,
|
03BCD7422488947200DA1F27 /* ProfileView.swift */,
|
||||||
03BCD7442488948200DA1F27 /* PostView.swift */,
|
03BCD7442488948200DA1F27 /* PostView.swift */,
|
||||||
|
03BCD7492489322500DA1F27 /* AttributedText.swift */,
|
||||||
);
|
);
|
||||||
path = MobileFort;
|
path = MobileFort;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
|
@ -155,6 +158,7 @@
|
||||||
03BCD7432488947200DA1F27 /* ProfileView.swift in Sources */,
|
03BCD7432488947200DA1F27 /* ProfileView.swift in Sources */,
|
||||||
03427F5B2488856C00A0073D /* SceneDelegate.swift in Sources */,
|
03427F5B2488856C00A0073D /* SceneDelegate.swift in Sources */,
|
||||||
03BCD7452488948200DA1F27 /* PostView.swift in Sources */,
|
03BCD7452488948200DA1F27 /* PostView.swift in Sources */,
|
||||||
|
03BCD74A2489322500DA1F27 /* AttributedText.swift in Sources */,
|
||||||
03427F5D2488856C00A0073D /* MainView.swift in Sources */,
|
03427F5D2488856C00A0073D /* MainView.swift in Sources */,
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
|
101
MobileFort/MobileFort/AttributedText.swift
Normal file
101
MobileFort/MobileFort/AttributedText.swift
Normal file
|
@ -0,0 +1,101 @@
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
extension NSMutableAttributedString {
|
||||||
|
func with(font: UIFont) -> NSMutableAttributedString {
|
||||||
|
enumerateAttribute(NSAttributedString.Key.font, in: NSMakeRange(0, length), options: .longestEffectiveRangeNotRequired, using: { (value, range, stop) in
|
||||||
|
if let originalFont = value as? UIFont, let newFont = applyTraitsFromFont(originalFont, to: font) {
|
||||||
|
addAttribute(NSAttributedString.Key.font, value: newFont, range: range)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return self
|
||||||
|
}
|
||||||
|
|
||||||
|
func applyTraitsFromFont(_ originalFont: UIFont, to newFont: UIFont) -> UIFont? {
|
||||||
|
let originalTrait = originalFont.fontDescriptor.symbolicTraits
|
||||||
|
|
||||||
|
if originalTrait.contains(.traitBold) {
|
||||||
|
var traits = newFont.fontDescriptor.symbolicTraits
|
||||||
|
traits.insert(.traitBold)
|
||||||
|
|
||||||
|
if let fontDescriptor = newFont.fontDescriptor.withSymbolicTraits(traits) {
|
||||||
|
return UIFont.init(descriptor: fontDescriptor, size: 0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return newFont
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
final class AttributedTextComponent: UIViewRepresentable {
|
||||||
|
let string: NSMutableAttributedString
|
||||||
|
|
||||||
|
init(_ string: NSMutableAttributedString) {
|
||||||
|
self.string = string
|
||||||
|
}
|
||||||
|
|
||||||
|
public func makeUIView(context: UIViewRepresentableContext<AttributedTextComponent>) -> UILabel {
|
||||||
|
UILabel()
|
||||||
|
}
|
||||||
|
|
||||||
|
func updateUIView(_ uiView: UILabel, context: Context) {
|
||||||
|
uiView.backgroundColor = .clear
|
||||||
|
uiView.numberOfLines = 0
|
||||||
|
uiView.lineBreakMode = .byWordWrapping
|
||||||
|
uiView.setContentCompressionResistancePriority(.defaultLow, for: .horizontal)
|
||||||
|
uiView.attributedText = string
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct SizeKey: PreferenceKey {
|
||||||
|
static var defaultValue: CGSize = .zero
|
||||||
|
|
||||||
|
static func reduce(value: inout CGSize, nextValue: () -> CGSize) {
|
||||||
|
value = nextValue()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct AttributedText: View {
|
||||||
|
let html: NSMutableAttributedString
|
||||||
|
let component: AttributedTextComponent
|
||||||
|
|
||||||
|
@State var height: CGFloat = 0.0
|
||||||
|
@State var lastSize: CGSize = .zero
|
||||||
|
|
||||||
|
init(_ html: NSMutableAttributedString) {
|
||||||
|
self.html = html.with(font: UIFont.preferredFont(forTextStyle: .body))
|
||||||
|
self.component = AttributedTextComponent(html)
|
||||||
|
}
|
||||||
|
|
||||||
|
func calculateHeight(size: CGSize) {
|
||||||
|
let label = UILabel(frame: CGRect(x: 0, y: 0, width: size.width, height: .greatestFiniteMagnitude))
|
||||||
|
|
||||||
|
label.numberOfLines = 0
|
||||||
|
label.lineBreakMode = .byWordWrapping
|
||||||
|
|
||||||
|
label.attributedText = self.html
|
||||||
|
|
||||||
|
label.sizeToFit()
|
||||||
|
|
||||||
|
self.height = label.frame.height
|
||||||
|
self.lastSize = size
|
||||||
|
}
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
ZStack {
|
||||||
|
GeometryReader { geometry in
|
||||||
|
Rectangle().fill(Color.clear).preference(key: SizeKey.self, value: geometry.size)
|
||||||
|
}.onPreferenceChange(SizeKey.self, perform: { size in
|
||||||
|
self.calculateHeight(size: size)
|
||||||
|
})
|
||||||
|
|
||||||
|
component
|
||||||
|
}
|
||||||
|
.frame(minWidth: 0.0, maxWidth: .infinity, minHeight: self.height, maxHeight: self.height)
|
||||||
|
.onAppear {
|
||||||
|
if self.lastSize != .zero {
|
||||||
|
self.calculateHeight(size: self.lastSize)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -7,79 +7,6 @@ extension String {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
final class AttributedTextComponent: UIViewRepresentable {
|
|
||||||
let string: NSMutableAttributedString
|
|
||||||
|
|
||||||
init(_ string: NSMutableAttributedString) {
|
|
||||||
self.string = string
|
|
||||||
}
|
|
||||||
|
|
||||||
public func makeUIView(context: UIViewRepresentableContext<AttributedTextComponent>) -> UILabel {
|
|
||||||
UILabel()
|
|
||||||
}
|
|
||||||
|
|
||||||
func updateUIView(_ uiView: UILabel, context: Context) {
|
|
||||||
uiView.backgroundColor = .clear
|
|
||||||
uiView.numberOfLines = 0
|
|
||||||
uiView.lineBreakMode = .byWordWrapping
|
|
||||||
uiView.setContentCompressionResistancePriority(.defaultLow, for: .horizontal)
|
|
||||||
uiView.attributedText = string
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct SizeKey: PreferenceKey {
|
|
||||||
static var defaultValue: CGSize = .zero
|
|
||||||
|
|
||||||
static func reduce(value: inout CGSize, nextValue: () -> CGSize) {
|
|
||||||
value = nextValue()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct AttributedText: View {
|
|
||||||
let html: NSMutableAttributedString
|
|
||||||
let component: AttributedTextComponent
|
|
||||||
|
|
||||||
@State var height: CGFloat = 0.0
|
|
||||||
@State var lastSize: CGSize = .zero
|
|
||||||
|
|
||||||
init(_ html: NSMutableAttributedString) {
|
|
||||||
self.html = html
|
|
||||||
self.component = AttributedTextComponent(html)
|
|
||||||
}
|
|
||||||
|
|
||||||
func calculateHeight(size: CGSize) {
|
|
||||||
let label = UILabel(frame: CGRect(x: 0, y: 0, width: size.width, height: .greatestFiniteMagnitude))
|
|
||||||
|
|
||||||
label.numberOfLines = 0
|
|
||||||
label.lineBreakMode = .byWordWrapping
|
|
||||||
|
|
||||||
label.attributedText = self.html
|
|
||||||
|
|
||||||
label.sizeToFit()
|
|
||||||
|
|
||||||
self.height = label.frame.height
|
|
||||||
self.lastSize = size
|
|
||||||
}
|
|
||||||
|
|
||||||
var body: some View {
|
|
||||||
ZStack {
|
|
||||||
GeometryReader { geometry in
|
|
||||||
Rectangle().fill(Color.clear).preference(key: SizeKey.self, value: geometry.size)
|
|
||||||
}.onPreferenceChange(SizeKey.self, perform: { size in
|
|
||||||
self.calculateHeight(size: size)
|
|
||||||
})
|
|
||||||
|
|
||||||
component
|
|
||||||
}
|
|
||||||
.frame(minWidth: 0.0, maxWidth: .infinity, minHeight: self.height, maxHeight: self.height)
|
|
||||||
.onAppear {
|
|
||||||
if self.lastSize != .zero {
|
|
||||||
self.calculateHeight(size: self.lastSize)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct PostView: View {
|
struct PostView: View {
|
||||||
let post: ParsedPostContainer
|
let post: ParsedPostContainer
|
||||||
|
|
||||||
|
|
|
@ -1,32 +1,5 @@
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
extension NSMutableAttributedString {
|
|
||||||
func with(font: UIFont) -> NSMutableAttributedString {
|
|
||||||
enumerateAttribute(NSAttributedString.Key.font, in: NSMakeRange(0, length), options: .longestEffectiveRangeNotRequired, using: { (value, range, stop) in
|
|
||||||
if let originalFont = value as? UIFont, let newFont = applyTraitsFromFont(originalFont, to: font) {
|
|
||||||
addAttribute(NSAttributedString.Key.font, value: newFont, range: range)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
return self
|
|
||||||
}
|
|
||||||
|
|
||||||
func applyTraitsFromFont(_ originalFont: UIFont, to newFont: UIFont) -> UIFont? {
|
|
||||||
let originalTrait = originalFont.fontDescriptor.symbolicTraits
|
|
||||||
|
|
||||||
if originalTrait.contains(.traitBold) {
|
|
||||||
var traits = newFont.fontDescriptor.symbolicTraits
|
|
||||||
traits.insert(.traitBold)
|
|
||||||
|
|
||||||
if let fontDescriptor = newFont.fontDescriptor.withSymbolicTraits(traits) {
|
|
||||||
return UIFont.init(descriptor: fontDescriptor, size: 0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return newFont
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct ProfileView: View {
|
struct ProfileView: View {
|
||||||
let username: String
|
let username: String
|
||||||
|
|
||||||
|
@ -55,7 +28,7 @@ struct ProfileView: View {
|
||||||
var postArray = [ParsedPostContainer]()
|
var postArray = [ParsedPostContainer]()
|
||||||
|
|
||||||
for post in decodedPosts.posts {
|
for post in decodedPosts.posts {
|
||||||
let container = ParsedPostContainer(post: post, contentAttributed: (try? NSMutableAttributedString(data: post.getContent().data(using: .utf8)!, options: [.documentType: NSAttributedString.DocumentType.html], documentAttributes: nil))!.with(font: UIFont.preferredFont(forTextStyle: .body)))
|
let container = ParsedPostContainer(post: post, contentAttributed: (try? NSMutableAttributedString(data: post.getContent().data(using: .utf8)!, options: [.documentType: NSAttributedString.DocumentType.html], documentAttributes: nil))!)
|
||||||
postArray.append(container)
|
postArray.append(container)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Reference in a new issue