Parse content at profile load to allow for better text layout
This commit is contained in:
parent
c42537d885
commit
c76c6fd0f1
3 changed files with 68 additions and 39 deletions
|
@ -54,6 +54,11 @@ struct Post: Decodable, Identifiable {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct ParsedPostContainer {
|
||||||
|
let post: Post
|
||||||
|
let contentAttributed: NSAttributedString
|
||||||
|
}
|
||||||
|
|
||||||
let mediaURL = "https://homepages.cae.wisc.edu/~ece533/images/airplane.png"
|
let mediaURL = "https://homepages.cae.wisc.edu/~ece533/images/airplane.png"
|
||||||
|
|
||||||
let testMedia = Media(id: 0,
|
let testMedia = Media(id: 0,
|
||||||
|
|
|
@ -7,44 +7,60 @@ extension String {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A custom view to use NSAttributedString in SwiftUI
|
final class AttributedTextComponent: UIViewRepresentable {
|
||||||
struct AttributedText: UIViewRepresentable {
|
let string: NSAttributedString
|
||||||
|
|
||||||
|
init(_ string: NSAttributedString) {
|
||||||
|
self.string = string
|
||||||
|
}
|
||||||
|
|
||||||
|
public func makeUIView(context: UIViewRepresentableContext<AttributedTextComponent>) -> UILabel {
|
||||||
|
UILabel()
|
||||||
|
}
|
||||||
|
|
||||||
func updateUIView(_ uiView: UILabel, context: Context) {
|
func updateUIView(_ uiView: UILabel, context: Context) {
|
||||||
|
uiView.backgroundColor = .clear
|
||||||
|
uiView.numberOfLines = 0
|
||||||
|
uiView.lineBreakMode = .byWordWrapping
|
||||||
|
uiView.setContentCompressionResistancePriority(.defaultLow, for: .horizontal)
|
||||||
|
uiView.attributedText = string
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let html: String
|
struct AttributedText: View {
|
||||||
|
let html: NSAttributedString
|
||||||
|
let component: AttributedTextComponent
|
||||||
|
|
||||||
init(_ html: String) {
|
@State var height: CGFloat = 0.0
|
||||||
|
|
||||||
|
init(_ html: NSAttributedString) {
|
||||||
self.html = html
|
self.html = html
|
||||||
|
self.component = AttributedTextComponent(html)
|
||||||
}
|
}
|
||||||
|
|
||||||
public func makeUIView(context: UIViewRepresentableContext<AttributedText>) -> UILabel {
|
var body: some View {
|
||||||
let textView = UILabel()
|
component.onAppear {
|
||||||
textView.backgroundColor = .clear
|
let label = UILabel()
|
||||||
textView.lineBreakMode = .byWordWrapping
|
|
||||||
textView.numberOfLines = 0
|
|
||||||
textView.lineBreakMode = .byWordWrapping
|
|
||||||
textView.setContentCompressionResistancePriority(.defaultLow, for: .horizontal)
|
|
||||||
|
|
||||||
DispatchQueue.main.async {
|
label.numberOfLines = 0
|
||||||
let data = Data(self.html.utf8)
|
label.lineBreakMode = .byWordWrapping
|
||||||
if let attributedString = try? NSAttributedString(data: data, options: [.documentType: NSAttributedString.DocumentType.html], documentAttributes: nil) {
|
label.setContentCompressionResistancePriority(.defaultLow, for: .horizontal)
|
||||||
textView.attributedText = attributedString
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return textView
|
label.attributedText = self.html
|
||||||
|
label.sizeToFit()
|
||||||
|
|
||||||
|
self.height = label.frame.height + 15.0 // for padding
|
||||||
|
}.frame(minWidth: 0.0, maxWidth: .infinity, minHeight: 0.0, maxHeight: height)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct PostView: View {
|
struct PostView: View {
|
||||||
let post: Post
|
let post: ParsedPostContainer
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
VStack {
|
VStack {
|
||||||
HStack {
|
HStack {
|
||||||
RemoteImage(type: .url(URL(string: post.avatarUrl.encodeUrl()!)!), errorView: { error in
|
RemoteImage(type: .url(URL(string: post.post.avatarUrl.encodeUrl()!)!), errorView: { error in
|
||||||
Text(error.localizedDescription)
|
Text(error.localizedDescription)
|
||||||
}, imageView: { image in
|
}, imageView: { image in
|
||||||
image
|
image
|
||||||
|
@ -55,12 +71,12 @@ struct PostView: View {
|
||||||
}).frame(width: 50.0, height: 50.0).padding(.leading)
|
}).frame(width: 50.0, height: 50.0).padding(.leading)
|
||||||
|
|
||||||
VStack(alignment: .leading) {
|
VStack(alignment: .leading) {
|
||||||
if post.isReblogged() {
|
if post.post.isReblogged() {
|
||||||
Text(post.username + " reblogged from " + post.originalUsername!).foregroundColor(.gray)
|
Text(post.post.username + " reblogged from " + post.post.originalUsername!).foregroundColor(.gray)
|
||||||
}
|
}
|
||||||
|
|
||||||
if post.getTitle() != nil {
|
if post.post.getTitle() != nil {
|
||||||
Text(post.getTitle()!)
|
Text(post.post.getTitle()!)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -68,7 +84,7 @@ struct PostView: View {
|
||||||
}.frame(maxWidth: .infinity)
|
}.frame(maxWidth: .infinity)
|
||||||
|
|
||||||
VStack {
|
VStack {
|
||||||
ForEach(post.media) { media in
|
ForEach(post.post.media) { media in
|
||||||
if !media.url.isEmpty {
|
if !media.url.isEmpty {
|
||||||
VStack {
|
VStack {
|
||||||
RemoteImage(type: .url(URL(string: media.url.encodeUrl()!)!), errorView: { error in
|
RemoteImage(type: .url(URL(string: media.url.encodeUrl()!)!), errorView: { error in
|
||||||
|
@ -83,15 +99,15 @@ struct PostView: View {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
AttributedText(post.getContent()).frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
AttributedText(post.contentAttributed)
|
||||||
}.frame(maxWidth: .infinity)
|
}.frame(maxWidth: .infinity)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct PostView_Previews: PreviewProvider {
|
struct PostView_Previews: PreviewProvider {
|
||||||
static var previews: some View {
|
static var previews: some View {
|
||||||
return PostView(post: fooPost)
|
return PostView(post: ParsedPostContainer(post: fooPost, contentAttributed: NSAttributedString()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,11 +3,11 @@ import SwiftUI
|
||||||
struct ProfileView: View {
|
struct ProfileView: View {
|
||||||
let username: String
|
let username: String
|
||||||
|
|
||||||
@State var posts: [Post] = []
|
@State var posts: [ParsedPostContainer] = []
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
VStack {
|
VStack {
|
||||||
List(posts) { post in
|
List(posts, id: \.post.id) { post in
|
||||||
PostView(post: post)
|
PostView(post: post)
|
||||||
}
|
}
|
||||||
}.navigationBarTitle(username + "'s Feed").onAppear {
|
}.navigationBarTitle(username + "'s Feed").onAppear {
|
||||||
|
@ -25,8 +25,15 @@ struct ProfileView: View {
|
||||||
|
|
||||||
let decodedPosts = try decoder.decode(Posts.self, from: jsonData)
|
let decodedPosts = try decoder.decode(Posts.self, from: jsonData)
|
||||||
|
|
||||||
|
var postArray = [ParsedPostContainer]()
|
||||||
|
|
||||||
|
for post in decodedPosts.posts {
|
||||||
|
let container = ParsedPostContainer(post: post, contentAttributed: (try? NSAttributedString(data: post.getContent().data(using: .utf8)!, options: [.documentType: NSAttributedString.DocumentType.html], documentAttributes: nil))!)
|
||||||
|
postArray.append(container)
|
||||||
|
}
|
||||||
|
|
||||||
DispatchQueue.main.sync {
|
DispatchQueue.main.sync {
|
||||||
self.posts = decodedPosts.posts
|
self.posts = postArray
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch {
|
} catch {
|
||||||
|
@ -39,6 +46,7 @@ struct ProfileView: View {
|
||||||
|
|
||||||
struct ProfileView_Previews: PreviewProvider {
|
struct ProfileView_Previews: PreviewProvider {
|
||||||
static var previews: some View {
|
static var previews: some View {
|
||||||
return ProfileView(username: "foobar", posts: [fooPost, fooPostReblog])
|
//return ProfileView(username: "foobar", posts: [fooPost, fooPostReblog])
|
||||||
|
Text("hello, world!")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Reference in a new issue