Avoid converting HTML to attributed string twice when displaying a status cell for the first time
Now, when Filterer performs the conversion, the status cell can reuse the attributed string.
This commit is contained in:
parent
b28f616e85
commit
54857a3bf3
|
@ -67,6 +67,7 @@
|
|||
D61F75B7293C119700C0B37F /* Filterer.swift in Sources */ = {isa = PBXBuildFile; fileRef = D61F75B6293C119700C0B37F /* Filterer.swift */; };
|
||||
D61F75B9293C15A000C0B37F /* ZeroHeightCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D61F75B8293C15A000C0B37F /* ZeroHeightCollectionViewCell.swift */; };
|
||||
D61F75BB293C183100C0B37F /* HTMLConverter.swift in Sources */ = {isa = PBXBuildFile; fileRef = D61F75BA293C183100C0B37F /* HTMLConverter.swift */; };
|
||||
D61F75BD293D099600C0B37F /* Lazy.swift in Sources */ = {isa = PBXBuildFile; fileRef = D61F75BC293D099600C0B37F /* Lazy.swift */; };
|
||||
D620483423D3801D008A63EF /* LinkTextView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D620483323D3801D008A63EF /* LinkTextView.swift */; };
|
||||
D620483623D38075008A63EF /* ContentTextView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D620483523D38075008A63EF /* ContentTextView.swift */; };
|
||||
D620483823D38190008A63EF /* StatusContentTextView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D620483723D38190008A63EF /* StatusContentTextView.swift */; };
|
||||
|
@ -450,6 +451,7 @@
|
|||
D61F75B6293C119700C0B37F /* Filterer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Filterer.swift; sourceTree = "<group>"; };
|
||||
D61F75B8293C15A000C0B37F /* ZeroHeightCollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZeroHeightCollectionViewCell.swift; sourceTree = "<group>"; };
|
||||
D61F75BA293C183100C0B37F /* HTMLConverter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HTMLConverter.swift; sourceTree = "<group>"; };
|
||||
D61F75BC293D099600C0B37F /* Lazy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Lazy.swift; sourceTree = "<group>"; };
|
||||
D620483323D3801D008A63EF /* LinkTextView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LinkTextView.swift; sourceTree = "<group>"; };
|
||||
D620483523D38075008A63EF /* ContentTextView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentTextView.swift; sourceTree = "<group>"; };
|
||||
D620483723D38190008A63EF /* StatusContentTextView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusContentTextView.swift; sourceTree = "<group>"; };
|
||||
|
@ -1463,6 +1465,7 @@
|
|||
D6E4269C2532A3E100C02E1C /* FuzzyMatcher.swift */,
|
||||
D61F75BA293C183100C0B37F /* HTMLConverter.swift */,
|
||||
D6B30E08254BAF63009CAEE5 /* ImageGrayscalifier.swift */,
|
||||
D61F75BC293D099600C0B37F /* Lazy.swift */,
|
||||
D60E2F2B24423EAD005F8713 /* LazilyDecoding.swift */,
|
||||
D64D0AAC2128D88B005A6F37 /* LocalData.swift */,
|
||||
D61DC84528F498F200B82C6E /* Logging.swift */,
|
||||
|
@ -2004,6 +2007,7 @@
|
|||
D6420AEE26BED18B00ED8175 /* PublicTimelineDescriptionTableViewCell.swift in Sources */,
|
||||
D6E0DC8E216EDF1E00369478 /* Previewing.swift in Sources */,
|
||||
D6B93667281D937300237D0E /* MainSidebarMyProfileCollectionViewCell.swift in Sources */,
|
||||
D61F75BD293D099600C0B37F /* Lazy.swift in Sources */,
|
||||
D6BED174212667E900F02DA0 /* TimelineStatusTableViewCell.swift in Sources */,
|
||||
D69693F42585941A00F4E116 /* UIWindowSceneDelegate+Close.swift in Sources */,
|
||||
D6C143DA253510F4007DC240 /* ComposeEmojiTextField.swift in Sources */,
|
||||
|
|
|
@ -42,7 +42,7 @@ class Filterer {
|
|||
|
||||
var filtersChanged: ((Bool) -> Void)?
|
||||
|
||||
private let htmlConverter = HTMLConverter()
|
||||
var htmlConverter = HTMLConverter()
|
||||
private var hasSetup = false
|
||||
private var matchers = [(NSRegularExpression, Result)]()
|
||||
private var allFiltersObserver: AnyCancellable?
|
||||
|
@ -114,16 +114,16 @@ class Filterer {
|
|||
}
|
||||
|
||||
// Use a closure for the status in case the result is cached and we don't need to look it up
|
||||
func resolve(state: FilterState, status: () -> StatusMO) -> Filterer.Result {
|
||||
func resolve(state: FilterState, status: () -> StatusMO) -> (Filterer.Result, NSAttributedString?) {
|
||||
switch state.state {
|
||||
case .known(_, generation: let knownGen) where knownGen < generation:
|
||||
fallthrough
|
||||
case .unknown:
|
||||
let result = doResolve(status: status())
|
||||
let (result, attributedString) = doResolve(status: status())
|
||||
state.state = .known(result, generation: generation)
|
||||
return result
|
||||
return (result, attributedString)
|
||||
case .known(let result, _):
|
||||
return result
|
||||
return (result, nil)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -131,21 +131,21 @@ class Filterer {
|
|||
state.state = .known(result, generation: generation)
|
||||
}
|
||||
|
||||
private func doResolve(status: StatusMO) -> Result {
|
||||
private func doResolve(status: StatusMO) -> (Result, NSAttributedString?) {
|
||||
if !hasSetup {
|
||||
setupFilters(filters: mastodonController.filters)
|
||||
}
|
||||
if matchers.isEmpty {
|
||||
return .allow
|
||||
return (.allow, nil)
|
||||
}
|
||||
lazy var text = htmlConverter.convert(status.content).string
|
||||
@Lazy var text = self.htmlConverter.convert(status.content)
|
||||
for (regex, result) in matchers {
|
||||
if (!status.spoilerText.isEmpty && regex.numberOfMatches(in: status.spoilerText, range: NSRange(location: 0, length: status.spoilerText.utf16.count)) > 0)
|
||||
|| regex.numberOfMatches(in: text, range: NSRange(location: 0, length: text.utf16.count)) > 0 {
|
||||
return result
|
||||
|| regex.numberOfMatches(in: text.string, range: NSRange(location: 0, length: text.length)) > 0 {
|
||||
return (result, _text.valueIfInitialized)
|
||||
}
|
||||
}
|
||||
return .allow
|
||||
return (.allow, _text.valueIfInitialized)
|
||||
}
|
||||
|
||||
enum Result: Equatable {
|
||||
|
|
|
@ -0,0 +1,54 @@
|
|||
//
|
||||
// Lazy.swift
|
||||
// Tusker
|
||||
//
|
||||
// Created by Shadowfacts on 12/4/22.
|
||||
// Copyright © 2022 Shadowfacts. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
/// A lazy initialization property wrapper that allows checking the initialization state.
|
||||
@propertyWrapper
|
||||
enum Lazy<Value> {
|
||||
case uninitialized(() -> Value)
|
||||
case initialized(Value)
|
||||
|
||||
init(wrappedValue: @autoclosure @escaping () -> Value) {
|
||||
self = .uninitialized(wrappedValue)
|
||||
}
|
||||
|
||||
/// Returns the contained value, initializing it if the value hasn't been accessed before.
|
||||
var wrappedValue: Value {
|
||||
mutating get {
|
||||
switch self {
|
||||
case .uninitialized(let closure):
|
||||
let value = closure()
|
||||
self = .initialized(value)
|
||||
return value
|
||||
case .initialized(let value):
|
||||
return value
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Whether this Lazy has been initialized yet.
|
||||
var isInitialized: Bool {
|
||||
switch self {
|
||||
case .uninitialized(_):
|
||||
return false
|
||||
case .initialized(_):
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
/// If this Lazy is initialized, this returns the value. Otherwise, it returns `nil`.
|
||||
var valueIfInitialized: Value? {
|
||||
switch self {
|
||||
case .uninitialized(_):
|
||||
return nil
|
||||
case .initialized(let value):
|
||||
return value
|
||||
}
|
||||
}
|
||||
}
|
|
@ -13,6 +13,7 @@ struct Logging {
|
|||
private init() {}
|
||||
|
||||
static let general = Logger(subsystem: Bundle.main.bundleIdentifier!, category: "General")
|
||||
static let generalSignposter = OSSignposter(logger: general)
|
||||
|
||||
static func getLogData() -> Data? {
|
||||
do {
|
||||
|
|
|
@ -65,7 +65,7 @@ class TrendingStatusesViewController: UIViewController {
|
|||
let statusCell = UICollectionView.CellRegistration<TimelineStatusCollectionViewCell, (String, CollapseState)> { [unowned self] cell, indexPath, item in
|
||||
cell.delegate = self
|
||||
// TODO: filter these
|
||||
cell.updateUI(statusID: item.0, state: item.1, filterResult: .allow)
|
||||
cell.updateUI(statusID: item.0, state: item.1, filterResult: .allow, precomputedContent: nil)
|
||||
}
|
||||
let loadingCell = UICollectionView.CellRegistration<LoadingCollectionViewCell, Void> { cell, _, _ in
|
||||
cell.indicator.startAnimating()
|
||||
|
|
|
@ -40,6 +40,8 @@ class ProfileStatusesViewController: UIViewController, TimelineLikeCollectionVie
|
|||
self.owner = owner
|
||||
self.mastodonController = owner.mastodonController
|
||||
self.filterer = Filterer(mastodonController: mastodonController, context: .account)
|
||||
self.filterer.htmlConverter.font = TimelineStatusCollectionViewCell.contentFont
|
||||
self.filterer.htmlConverter.paragraphStyle = TimelineStatusCollectionViewCell.contentParagraphStyle
|
||||
|
||||
super.init(nibName: nil, bundle: nil)
|
||||
|
||||
|
@ -116,10 +118,10 @@ class ProfileStatusesViewController: UIViewController, TimelineLikeCollectionVie
|
|||
|
||||
private func createDataSource() -> UICollectionViewDiffableDataSource<Section, Item> {
|
||||
collectionView.register(ProfileHeaderCollectionViewCell.self, forCellWithReuseIdentifier: "headerCell")
|
||||
let statusCell = UICollectionView.CellRegistration<TimelineStatusCollectionViewCell, (String, CollapseState, Filterer.Result, Bool)> { [unowned self] cell, indexPath, item in
|
||||
let statusCell = UICollectionView.CellRegistration<TimelineStatusCollectionViewCell, (String, CollapseState, Filterer.Result, NSAttributedString?, Bool)> { [unowned self] cell, indexPath, item in
|
||||
cell.delegate = self
|
||||
cell.showPinned = item.3
|
||||
cell.updateUI(statusID: item.0, state: item.1, filterResult: item.2)
|
||||
cell.showPinned = item.4
|
||||
cell.updateUI(statusID: item.0, state: item.1, filterResult: item.2, precomputedContent: item.3)
|
||||
}
|
||||
return UICollectionViewDiffableDataSource(collectionView: collectionView) { [unowned self] collectionView, indexPath, itemIdentifier in
|
||||
switch itemIdentifier {
|
||||
|
@ -146,8 +148,8 @@ class ProfileStatusesViewController: UIViewController, TimelineLikeCollectionVie
|
|||
return cell
|
||||
}
|
||||
case .status(id: let id, collapseState: let collapseState, filterState: let filterState, pinned: let pinned):
|
||||
let result = filterResult(state: filterState, statusID: id)
|
||||
return collectionView.dequeueConfiguredReusableCell(using: statusCell, for: indexPath, item: (id, collapseState, result, pinned))
|
||||
let (result, precomputedContent) = filterResult(state: filterState, statusID: id)
|
||||
return collectionView.dequeueConfiguredReusableCell(using: statusCell, for: indexPath, item: (id, collapseState, result, precomputedContent, pinned))
|
||||
case .loadingIndicator:
|
||||
return loadingIndicatorCell(for: indexPath)
|
||||
case .confirmLoadMore:
|
||||
|
@ -245,7 +247,7 @@ class ProfileStatusesViewController: UIViewController, TimelineLikeCollectionVie
|
|||
await apply(snapshot, animatingDifferences: true)
|
||||
}
|
||||
|
||||
private func filterResult(state: FilterState, statusID: String) -> Filterer.Result {
|
||||
private func filterResult(state: FilterState, statusID: String) -> (Filterer.Result, NSAttributedString?) {
|
||||
let status = {
|
||||
let status = self.mastodonController.persistentContainer.status(for: statusID)!
|
||||
// if the status is a reblog of another one, filter based on that one
|
||||
|
|
|
@ -80,7 +80,7 @@ class StatusActionAccountListViewController: UIViewController {
|
|||
private func createDataSource() -> UICollectionViewDiffableDataSource<Section, Item> {
|
||||
let statusCell = UICollectionView.CellRegistration<TimelineStatusCollectionViewCell, Void> { [unowned self] cell, indexPath, _ in
|
||||
cell.delegate = self
|
||||
cell.updateUI(statusID: self.statusID, state: self.statusState, filterResult: .allow)
|
||||
cell.updateUI(statusID: self.statusID, state: self.statusState, filterResult: .allow, precomputedContent: nil)
|
||||
}
|
||||
let accountCell = UICollectionView.CellRegistration<AccountCollectionViewCell, String> { [unowned self] cell, indexPath, item in
|
||||
cell.delegate = self
|
||||
|
|
|
@ -69,10 +69,10 @@ class InstanceTimelineViewController: TimelineViewController {
|
|||
toggleSaveButton.title = toggleSaveButtonTitle
|
||||
}
|
||||
|
||||
override func configureStatusCell(_ cell: TimelineStatusCollectionViewCell, id: String, state: CollapseState, filterResult: Filterer.Result) {
|
||||
override func configureStatusCell(_ cell: TimelineStatusCollectionViewCell, id: String, state: CollapseState, filterResult: Filterer.Result, precomputedContent: NSAttributedString?) {
|
||||
cell.delegate = browsingEnabled ? self : nil
|
||||
cell.overrideMastodonController = mastodonController
|
||||
cell.updateUI(statusID: id, state: state, filterResult: filterResult)
|
||||
cell.updateUI(statusID: id, state: state, filterResult: filterResult, precomputedContent: precomputedContent)
|
||||
}
|
||||
|
||||
override func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
|
||||
|
|
|
@ -37,6 +37,8 @@ class TimelineViewController: UIViewController, TimelineLikeCollectionViewContro
|
|||
filterContext = .public
|
||||
}
|
||||
self.filterer = Filterer(mastodonController: mastodonController, context: filterContext)
|
||||
self.filterer.htmlConverter.font = TimelineStatusCollectionViewCell.contentFont
|
||||
self.filterer.htmlConverter.paragraphStyle = TimelineStatusCollectionViewCell.contentParagraphStyle
|
||||
|
||||
super.init(nibName: nil, bundle: nil)
|
||||
|
||||
|
@ -69,7 +71,8 @@ class TimelineViewController: UIViewController, TimelineLikeCollectionViewContro
|
|||
config.topSeparatorVisibility = .hidden
|
||||
config.bottomSeparatorVisibility = .hidden
|
||||
} else if case .status(id: let id, collapseState: _, filterState: let filterState) = item,
|
||||
case .hide = filterResult(state: filterState, statusID: id) {
|
||||
case (.hide, _) = filterResult(state: filterState, statusID: id) {
|
||||
// this runs after the cell is setup, so the filter state is already known and this check is cheap
|
||||
config.topSeparatorVisibility = .hidden
|
||||
config.bottomSeparatorVisibility = .hidden
|
||||
} else {
|
||||
|
@ -116,19 +119,19 @@ class TimelineViewController: UIViewController, TimelineLikeCollectionViewContro
|
|||
}
|
||||
|
||||
// separate method because InstanceTimelineViewController needs to be able to customize it
|
||||
func configureStatusCell(_ cell: TimelineStatusCollectionViewCell, id: String, state: CollapseState, filterResult: Filterer.Result) {
|
||||
func configureStatusCell(_ cell: TimelineStatusCollectionViewCell, id: String, state: CollapseState, filterResult: Filterer.Result, precomputedContent: NSAttributedString?) {
|
||||
cell.delegate = self
|
||||
if case .home = timeline {
|
||||
cell.showFollowedHashtags = true
|
||||
} else {
|
||||
cell.showFollowedHashtags = false
|
||||
}
|
||||
cell.updateUI(statusID: id, state: state, filterResult: filterResult)
|
||||
cell.updateUI(statusID: id, state: state, filterResult: filterResult, precomputedContent: precomputedContent)
|
||||
}
|
||||
|
||||
private func createDataSource() -> UICollectionViewDiffableDataSource<Section, Item> {
|
||||
let statusCell = UICollectionView.CellRegistration<TimelineStatusCollectionViewCell, (String, CollapseState, Filterer.Result)> { [unowned self] cell, indexPath, item in
|
||||
self.configureStatusCell(cell, id: item.0, state: item.1, filterResult: item.2)
|
||||
let statusCell = UICollectionView.CellRegistration<TimelineStatusCollectionViewCell, (String, CollapseState, Filterer.Result, NSAttributedString?)> { [unowned self] cell, indexPath, item in
|
||||
self.configureStatusCell(cell, id: item.0, state: item.1, filterResult: item.2, precomputedContent: item.3)
|
||||
}
|
||||
let zeroHeightCell = UICollectionView.CellRegistration<ZeroHeightCollectionViewCell, Void> { _, _, _ in
|
||||
}
|
||||
|
@ -148,10 +151,10 @@ class TimelineViewController: UIViewController, TimelineLikeCollectionViewContro
|
|||
return UICollectionViewDiffableDataSource(collectionView: collectionView) { [unowned self] collectionView, indexPath, itemIdentifier in
|
||||
switch itemIdentifier {
|
||||
case .status(id: let id, collapseState: let state, filterState: let filterState):
|
||||
let result = filterResult(state: filterState, statusID: id)
|
||||
let (result, attributedString) = filterResult(state: filterState, statusID: id)
|
||||
switch result {
|
||||
case .allow, .warn(_):
|
||||
return collectionView.dequeueConfiguredReusableCell(using: statusCell, for: indexPath, item: (id, state, result))
|
||||
return collectionView.dequeueConfiguredReusableCell(using: statusCell, for: indexPath, item: (id, state, result, nil))
|
||||
case .hide:
|
||||
return collectionView.dequeueConfiguredReusableCell(using: zeroHeightCell, for: indexPath, item: ())
|
||||
}
|
||||
|
@ -329,7 +332,7 @@ class TimelineViewController: UIViewController, TimelineLikeCollectionViewContro
|
|||
isShowingTimelineDescription = false
|
||||
}
|
||||
|
||||
private func filterResult(state: FilterState, statusID: String) -> Filterer.Result {
|
||||
private func filterResult(state: FilterState, statusID: String) -> (Filterer.Result, NSAttributedString?) {
|
||||
let status = {
|
||||
let status = self.mastodonController.persistentContainer.status(for: statusID)!
|
||||
// if the status is a reblog of another one, filter based on that one
|
||||
|
|
|
@ -76,14 +76,14 @@ extension StatusCollectionViewCell {
|
|||
.store(in: &cancellables)
|
||||
}
|
||||
|
||||
func doUpdateUI(status: StatusMO) {
|
||||
func doUpdateUI(status: StatusMO, precomputedContent: NSAttributedString? = nil) {
|
||||
statusID = status.id
|
||||
accountID = status.account.id
|
||||
|
||||
updateAccountUI(account: status.account)
|
||||
updateUIForPreferences(status: status)
|
||||
|
||||
contentContainer.contentTextView.setTextFrom(status: status)
|
||||
contentContainer.contentTextView.setTextFrom(status: status, precomputed: precomputedContent)
|
||||
contentContainer.contentTextView.navigationDelegate = delegate
|
||||
contentContainer.attachmentsView.delegate = self
|
||||
contentContainer.attachmentsView.updateUI(status: status)
|
||||
|
@ -173,7 +173,7 @@ extension StatusCollectionViewCell {
|
|||
func updateGrayscaleableUI(status: StatusMO) {
|
||||
isGrayscale = Preferences.shared.grayscaleImages
|
||||
if contentContainer.contentTextView.hasEmojis {
|
||||
contentContainer.contentTextView.setTextFrom(status: status)
|
||||
contentContainer.contentTextView.setEmojis(status.emojis, identifier: status.id)
|
||||
}
|
||||
displayNameLabel.updateForAccountDisplayName(account: status.account)
|
||||
}
|
||||
|
|
|
@ -11,7 +11,6 @@ import UIKit
|
|||
class StatusContentContainer: UIView {
|
||||
|
||||
let contentTextView = StatusContentTextView().configure {
|
||||
$0.defaultFont = UIFontMetrics(forTextStyle: .body).scaledFont(for: .systemFont(ofSize: 16))
|
||||
$0.adjustsFontForContentSizeCategory = true
|
||||
$0.isScrollEnabled = false
|
||||
$0.backgroundColor = nil
|
||||
|
|
|
@ -16,6 +16,8 @@ private let hashtagIcon = UIImage(systemName: "number")
|
|||
class TimelineStatusCollectionViewCell: UICollectionViewListCell, StatusCollectionViewCell {
|
||||
|
||||
static let separatorInsets = NSDirectionalEdgeInsets(top: 0, leading: 16, bottom: 0, trailing: 0)
|
||||
static let contentFont = UIFontMetrics(forTextStyle: .body).scaledFont(for: .systemFont(ofSize: 16))
|
||||
static let contentParagraphStyle = HTMLConverter.defaultParagraphStyle
|
||||
|
||||
// MARK: Subviews
|
||||
|
||||
|
@ -164,6 +166,8 @@ class TimelineStatusCollectionViewCell: UICollectionViewListCell, StatusCollecti
|
|||
}
|
||||
|
||||
let contentContainer = StatusContentContainer().configure {
|
||||
$0.contentTextView.font = TimelineStatusCollectionViewCell.contentFont
|
||||
$0.contentTextView.paragraphStyle = TimelineStatusCollectionViewCell.contentParagraphStyle
|
||||
$0.setContentHuggingPriority(.defaultLow, for: .vertical)
|
||||
}
|
||||
private var contentTextView: StatusContentTextView {
|
||||
|
@ -478,8 +482,7 @@ class TimelineStatusCollectionViewCell: UICollectionViewListCell, StatusCollecti
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
func updateUI(statusID: String, state: CollapseState, filterResult: Filterer.Result) {
|
||||
func updateUI(statusID: String, state: CollapseState, filterResult: Filterer.Result, precomputedContent: NSAttributedString?) {
|
||||
guard var status = mastodonController.persistentContainer.status(for: statusID) else {
|
||||
fatalError()
|
||||
}
|
||||
|
@ -531,7 +534,7 @@ class TimelineStatusCollectionViewCell: UICollectionViewListCell, StatusCollecti
|
|||
mainContainerTopToReblogLabelConstraint.isActive = !hideTimelineReason
|
||||
mainContainerTopToSelfConstraint.isActive = hideTimelineReason
|
||||
|
||||
doUpdateUI(status: status)
|
||||
doUpdateUI(status: status, precomputedContent: precomputedContent)
|
||||
|
||||
doUpdateTimestamp(status: status)
|
||||
timestampLabel.isHidden = showPinned
|
||||
|
|
|
@ -14,9 +14,13 @@ class StatusContentTextView: ContentTextView {
|
|||
|
||||
private var statusID: String?
|
||||
|
||||
func setTextFrom(status: StatusMO) {
|
||||
func setTextFrom(status: StatusMO, precomputed attributedText: NSAttributedString? = nil) {
|
||||
statusID = status.id
|
||||
if let attributedText {
|
||||
self.attributedText = attributedText
|
||||
} else {
|
||||
setTextFromHtml(status.content)
|
||||
}
|
||||
setEmojis(status.emojis, identifier: status.id)
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue