mirror of
https://github.com/element-hq/element-x-ios.git
synced 2025-03-11 13:59:13 +00:00
Pills performance improvements (#1891)
* big performance improvement for pills * small overall performance improvement * code improvements
This commit is contained in:
parent
f97a7c8cce
commit
8e03b89641
@ -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)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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 }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -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)
|
||||||
|
@ -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 {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user