Pills performance improvements (#1891)

* big performance improvement for pills

* small overall performance improvement

* code improvements
This commit is contained in:
Mauro 2023-10-13 14:40:18 +02:00 committed by GitHub
parent f97a7c8cce
commit 8e03b89641
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 50 additions and 58 deletions

View File

@ -19,6 +19,7 @@ import SwiftUI
final class MessageTextView: UITextView, PillAttachmentViewProviderDelegate { final class MessageTextView: UITextView, PillAttachmentViewProviderDelegate {
var roomContext: RoomScreenViewModel.Context? var roomContext: RoomScreenViewModel.Context?
var updateClosure: (() -> Void)? var updateClosure: (() -> Void)?
private var pillViews = NSHashTable<UIView>.weakObjects()
// This prevents the magnifying glass from showing up // This prevents the magnifying glass from showing up
override func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool { override func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool {
@ -38,21 +39,23 @@ final class MessageTextView: UITextView, PillAttachmentViewProviderDelegate {
} }
} }
} }
// Required to setup the first rendering of the pill view
override func layoutSubviews() {
invalidateTextAttachmentsDisplay(update: false)
super.layoutSubviews()
}
func registerPillView(_ pillView: UIView) { func registerPillView(_ pillView: UIView) {
// No need to be implemented in this view pillViews.add(pillView)
}
func flushPills() {
for view in pillViews.allObjects {
view.alpha = 0.0
view.removeFromSuperview()
}
pillViews.removeAllObjects()
} }
} }
struct MessageText: UIViewRepresentable { struct MessageText: UIViewRepresentable {
@Environment(\.openURL) private var openURLAction: OpenURLAction @Environment(\.openURL) private var openURLAction
@EnvironmentObject private var viewModel: RoomScreenViewModel.Context @Environment(\.roomContext) private var viewModel
@State private var computedSizes = [Double: CGSize]() @State private var computedSizes = [Double: CGSize]()
@State var attributedString: AttributedString { @State var attributedString: AttributedString {
@ -92,7 +95,11 @@ struct MessageText: UIViewRepresentable {
} }
func updateUIView(_ uiView: MessageTextView, context: Context) { func updateUIView(_ uiView: MessageTextView, context: Context) {
uiView.attributedText = NSAttributedString(attributedString) let newAttributedText = NSAttributedString(attributedString)
if uiView.attributedText != newAttributedText {
uiView.flushPills()
uiView.attributedText = newAttributedText
}
context.coordinator.openURLAction = openURLAction context.coordinator.openURLAction = openURLAction
} }
@ -177,7 +184,6 @@ struct MessageText_Previews: PreviewProvider, TestablePreview {
MessageText(attributedString: attributedString) MessageText(attributedString: attributedString)
.border(Color.purple) .border(Color.purple)
.previewDisplayName("Custom Text") .previewDisplayName("Custom Text")
.environmentObject(RoomScreenViewModel.mock.context)
// For comparison // For comparison
Text(attributedString) Text(attributedString)
.border(Color.purple) .border(Color.purple)
@ -188,13 +194,11 @@ struct MessageText_Previews: PreviewProvider, TestablePreview {
MessageText(attributedString: attributedString) MessageText(attributedString: attributedString)
.border(Color.purple) .border(Color.purple)
.previewDisplayName("With block quote") .previewDisplayName("With block quote")
.environmentObject(RoomScreenViewModel.mock.context)
} }
if let attributedString = attributedStringBuilder.fromHTML(htmlStringWithList) { if let attributedString = attributedStringBuilder.fromHTML(htmlStringWithList) {
MessageText(attributedString: attributedString) MessageText(attributedString: attributedString)
.border(Color.purple) .border(Color.purple)
.previewDisplayName("With list") .previewDisplayName("With list")
.environmentObject(RoomScreenViewModel.mock.context)
} }
} }
} }

View File

@ -22,22 +22,17 @@ final class PillTextAttachment: NSTextAttachment {
let encoder = JSONEncoder() let encoder = JSONEncoder()
guard let encodedData = try? encoder.encode(attachmentData) else { return nil } guard let encodedData = try? encoder.encode(attachmentData) else { return nil }
self.init(data: encodedData, ofType: InfoPlistReader.main.pillsUTType) self.init(data: encodedData, ofType: InfoPlistReader.main.pillsUTType)
pillData = attachmentData
} }
var pillData: PillTextAttachmentData? { private(set) var pillData: PillTextAttachmentData!
guard let contents else {
return nil
}
let decoder = JSONDecoder()
return try? decoder.decode(PillTextAttachmentData.self, from: contents)
}
override func attachmentBounds(for textContainer: NSTextContainer?, proposedLineFragment lineFrag: CGRect, glyphPosition position: CGPoint, characterIndex charIndex: Int) -> CGRect { override func attachmentBounds(for textContainer: NSTextContainer?, proposedLineFragment lineFrag: CGRect, glyphPosition position: CGPoint, characterIndex charIndex: Int) -> CGRect {
var rect = super.attachmentBounds(for: textContainer, proposedLineFragment: lineFrag, glyphPosition: position, characterIndex: charIndex) var rect = super.attachmentBounds(for: textContainer, proposedLineFragment: lineFrag, glyphPosition: position, characterIndex: charIndex)
if let font = pillData?.font {
// Align the pill text vertically with the surrounding text. let fontData = pillData.fontData
rect.origin.y = font.descender + (font.lineHeight - rect.height) / 2.0 // Align the pill text vertically with the surrounding text.
} rect.origin.y = fontData.descender + (fontData.lineHeight - rect.height) / 2.0
return rect return rect
} }
} }

View File

@ -24,43 +24,22 @@ enum PillType: Codable, Equatable {
case allUsers case allUsers
} }
struct PillTextAttachmentData { struct PillTextAttachmentData: Codable, Equatable {
// MARK: - Properties struct Font: Codable, Equatable {
let descender: CGFloat
let lineHeight: CGFloat
}
/// Pill type /// Pill type
let type: PillType let type: PillType
/// Font for the display name /// Font for the display name
let font: UIFont let fontData: Font
} }
extension PillTextAttachmentData: Codable { extension PillTextAttachmentData {
// MARK: - Codable init(type: PillType, font: UIFont) {
self.type = type
enum CodingKeys: String, CodingKey { fontData = Font(descender: font.descender, lineHeight: font.lineHeight)
case type
case font
}
enum PillTextAttachmentDataError: Error {
case noFontData
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
type = try container.decode(PillType.self, forKey: .type)
let fontData = try container.decode(Data.self, forKey: .font)
if let font = try NSKeyedUnarchiver.unarchivedObject(ofClass: UIFont.self, from: fontData) {
self.font = font
} else {
throw PillTextAttachmentDataError.noFontData
}
}
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(type, forKey: .type)
let fontData = try NSKeyedArchiver.archivedData(withRootObject: font, requiringSecureCoding: false)
try container.encode(fontData, forKey: .font)
} }
} }

View File

@ -925,3 +925,16 @@ private struct ReplyInfo {
let type: EventBasedMessageTimelineItemContentType let type: EventBasedMessageTimelineItemContentType
let isThread: Bool let isThread: Bool
} }
private struct RoomContextKey: EnvironmentKey {
@MainActor
static let defaultValue = RoomScreenViewModel.mock.context
}
extension EnvironmentValues {
/// Used to access and inject and access the room context without observing it
var roomContext: RoomScreenViewModel.Context {
get { self[RoomContextKey.self] }
set { self[RoomContextKey.self] = newValue }
}
}

View File

@ -163,6 +163,7 @@ class TimelineTableViewController: UIViewController {
.id(id) .id(id)
.frame(maxWidth: .infinity, alignment: .leading) .frame(maxWidth: .infinity, alignment: .leading)
.environmentObject(coordinator.context) // Attempted fix at a crash in TimelineItemContextMenu .environmentObject(coordinator.context) // Attempted fix at a crash in TimelineItemContextMenu
.environment(\.roomContext, coordinator.context)
} }
.margins(.all, self.timelineStyle.rowInsets) .margins(.all, self.timelineStyle.rowInsets)
.minSize(height: 1) .minSize(height: 1)

View File

@ -16,7 +16,7 @@
import SwiftUI import SwiftUI
struct RoomTimelineItemView: View { struct RoomTimelineItemView: View {
@EnvironmentObject private var context: RoomScreenViewModel.Context @Environment(\.roomContext) var context: RoomScreenViewModel.Context
@ObservedObject var viewState: RoomTimelineItemViewState @ObservedObject var viewState: RoomTimelineItemViewState
var body: some View { var body: some View {