Skip to content

Commit

Permalink
chore: avoid object create during scrolling
Browse files Browse the repository at this point in the history
  • Loading branch information
MainasuK committed Sep 12, 2023
1 parent 70bb66c commit ae34f79
Show file tree
Hide file tree
Showing 4 changed files with 106 additions and 79 deletions.
119 changes: 69 additions & 50 deletions TwidereSDK/Sources/TwidereUI/Content/StatusToolbarView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -65,14 +65,7 @@ extension StatusToolbarView {
handler(action)
},
action: .reply,
image: {
switch viewModel.style {
case .inline:
return Asset.Communication.textBubbleMini.image.withRenderingMode(.alwaysTemplate)
case .plain:
return Asset.Communication.textBubble.image.withRenderingMode(.alwaysTemplate)
}
}(),
image: viewModel.replyButtonImage,
count: isMetricCountDisplay ? viewModel.replyCount : nil,
tintColor: nil
)
Expand All @@ -83,26 +76,6 @@ extension StatusToolbarView {
case repostOff
case repostLock

func image(style: StatusToolbarView.Style) -> UIImage {
switch self {
case .repost:
switch style {
case .inline: return Asset.Media.repeatMini.image.withRenderingMode(.alwaysTemplate)
case .plain: return Asset.Media.repeat.image.withRenderingMode(.alwaysTemplate)
}
case .repostOff:
switch style {
case .inline: return Asset.Media.repeatOffMini.image.withRenderingMode(.alwaysTemplate)
case .plain: return Asset.Media.repeatOff.image.withRenderingMode(.alwaysTemplate)
}
case .repostLock:
switch style {
case .inline: return Asset.Media.repeatLockMini.image.withRenderingMode(.alwaysTemplate)
case .plain: return Asset.Media.repeatLock.image.withRenderingMode(.alwaysTemplate)
}
} // end switch
} // end func

static func kind(
platform: Platform,
isReposeRestricted: Bool,
Expand Down Expand Up @@ -133,11 +106,12 @@ extension StatusToolbarView {
},
action: .repost,
image: {
return RepostButtonImage.kind(
let kind = RepostButtonImage.kind(
platform: viewModel.platform,
isReposeRestricted: viewModel.isReposeRestricted,
isMyself: viewModel.isMyself
).image(style: viewModel.style)
)
return viewModel.repostButtonImage(kind: kind)
}(),
count: isMetricCountDisplay ? viewModel.repostCount : nil,
tintColor: viewModel.isReposted ? Asset.Scene.Status.Toolbar.repost.color : nil
Expand Down Expand Up @@ -185,15 +159,7 @@ extension StatusToolbarView {
handler(action)
},
action: .like,
image: {
switch viewModel.style {
case .inline:
return viewModel.isLiked ? Asset.Health.heartFillMini.image.withRenderingMode(.alwaysTemplate) : Asset.Health.heartMini.image.withRenderingMode(.alwaysTemplate)
case .plain:
return viewModel.isLiked ? Asset.Health.heartFill.image.withRenderingMode(.alwaysTemplate) : Asset.Health.heart.image.withRenderingMode(.alwaysTemplate)
}

}(),
image: viewModel.isLiked ? viewModel.likeOnButtonImage : viewModel.likeOffButtonImage,
count: isMetricCountDisplay ? viewModel.likeCount : nil,
tintColor: viewModel.isLiked ? Asset.Scene.Status.Toolbar.like.color : nil
)
Expand All @@ -215,15 +181,7 @@ extension StatusToolbarView {
} // end ForEach
} label: {
HStack {
let image: UIImage = {
switch viewModel.style {
case .inline:
return Asset.Editing.ellipsisMini.image.withRenderingMode(.alwaysTemplate)
case .plain:
return Asset.Editing.ellipsis.image.withRenderingMode(.alwaysTemplate)
}
}()
Image(uiImage: image)
Image(uiImage: viewModel.moreButtonImage)
.foregroundColor(.secondary)
}
}
Expand All @@ -237,8 +195,9 @@ extension StatusToolbarView {
public let menuButtonBackgroundView = UIView()

// input
let style: Style

@Published var platform: Platform = .none
@Published var style: Style = .inline

@Published var replyCount: Int?
@Published var repostCount: Int?
Expand All @@ -250,13 +209,73 @@ extension StatusToolbarView {
@Published var isReposeRestricted: Bool = false
@Published var isMyself: Bool = false

// output
let replyButtonImage: UIImage
let repostButtonImage: UIImage
let repostOffButtonImage: UIImage
let repostLockButtonImage: UIImage
let likeOnButtonImage: UIImage
let likeOffButtonImage: UIImage
let moreButtonImage: UIImage

var isRepostable: Bool {
return isMyself || !isReposeRestricted
}

public init() {
public init(style: Style) {
self.style = style
self.replyButtonImage = {
switch style {
case .inline: return Asset.Communication.textBubbleMini.image.withRenderingMode(.alwaysTemplate)
case .plain: return Asset.Communication.textBubble.image.withRenderingMode(.alwaysTemplate)
}
}()
self.repostButtonImage = {
switch style {
case .inline: return Asset.Media.repeatMini.image.withRenderingMode(.alwaysTemplate)
case .plain: return Asset.Media.repeat.image.withRenderingMode(.alwaysTemplate)
}
}()
self.repostOffButtonImage = {
switch style {
case .inline: return Asset.Media.repeatOffMini.image.withRenderingMode(.alwaysTemplate)
case .plain: return Asset.Media.repeatOff.image.withRenderingMode(.alwaysTemplate)
}
}()
self.repostLockButtonImage = {
switch style {
case .inline: return Asset.Media.repeatLockMini.image.withRenderingMode(.alwaysTemplate)
case .plain: return Asset.Media.repeatLock.image.withRenderingMode(.alwaysTemplate)
}
}()
self.likeOnButtonImage = {
switch style {
case .inline: return Asset.Health.heartFillMini.image.withRenderingMode(.alwaysTemplate)
case .plain: return Asset.Health.heartFill.image.withRenderingMode(.alwaysTemplate)
}
}()
self.likeOffButtonImage = {
switch style {
case .inline: return Asset.Health.heartMini.image.withRenderingMode(.alwaysTemplate)
case .plain: return Asset.Health.heart.image.withRenderingMode(.alwaysTemplate)
}
}()
self.moreButtonImage = {
switch style {
case .inline: return Asset.Editing.ellipsisMini.image.withRenderingMode(.alwaysTemplate)
case .plain: return Asset.Editing.ellipsis.image.withRenderingMode(.alwaysTemplate)
}
}()
// end init
}

func repostButtonImage(kind: RepostButtonImage) -> UIImage {
switch kind {
case .repost: return repostButtonImage
case .repostOff: return repostOffButtonImage
case .repostLock: return repostLockButtonImage
}
}
}
}

Expand Down
58 changes: 34 additions & 24 deletions TwidereSDK/Sources/TwidereUI/Content/StatusView+ViewModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -65,8 +65,11 @@ extension StatusView {

// content
@Published public var spoilerContent: MetaContent?
@Published public var spoilerContentAttributedString: AttributedString?
@Published public var isSpoilerContentContainsMeta: Bool = false

@Published public var content: MetaContent = PlaintextMetaContent(string: "")
@Published public var contentAttributedString = AttributedString("")
@Published public var isContentContainsMeta: Bool = false

var isContentEmpty: Bool { content.string.isEmpty }
Expand Down Expand Up @@ -122,23 +125,7 @@ extension StatusView {

// visibility
@Published public var visibility: MastodonVisibility?
var visibilityIconImage: UIImage? {
switch visibility {
case .public:
return Asset.ObjectTools.globeMiniInline.image.withRenderingMode(.alwaysTemplate)
case .unlisted:
return Asset.ObjectTools.lockOpenMiniInline.image.withRenderingMode(.alwaysTemplate)
case .private:
return Asset.ObjectTools.lockMiniInline.image.withRenderingMode(.alwaysTemplate)
case .direct:
return Asset.Communication.mailMiniInline.image.withRenderingMode(.alwaysTemplate)
case ._other:
assertionFailure()
return nil
case nil:
return nil
}
}
@Published public var visibilityIconImage: UIImage?

// @Published public var groupedAccessibilityLabel = ""

Expand All @@ -152,7 +139,7 @@ extension StatusView {
@Published public var metricViewModel: StatusMetricView.ViewModel?

// toolbar
public let toolbarViewModel = StatusToolbarView.ViewModel()
public let toolbarViewModel: StatusToolbarView.ViewModel
public var canDelete: Bool {
guard let authContext = self.authContext else { return false }
guard let authorUserIdentifier = self.authorUserIdentifier else { return false }
Expand Down Expand Up @@ -192,6 +179,7 @@ extension StatusView {
guard let myUserIdentifier = authContext?.authenticationContext.userIdentifier else { return false }
return myUserIdentifier == _authorUserIdentifier
}()
self.toolbarViewModel = StatusToolbarView.ViewModel(style: kind == .conversationRoot ? .plain : .inline)
// end init

viewLayoutFramePublisher?.assign(to: &$viewLayoutFrame)
Expand All @@ -208,6 +196,7 @@ extension StatusView {
self.kind = .timeline
self.authorUserIdentifier = nil
self.isMyself = false
self.toolbarViewModel = StatusToolbarView.ViewModel(style: kind == .conversationRoot ? .plain : .inline)
// end init

viewLayoutFramePublisher?.assign(to: &$viewLayoutFrame)
Expand All @@ -224,9 +213,6 @@ extension StatusView {
UserDefaults.shared.publisher(for: \.translateButtonPreference)
.map { $0 }
.assign(to: &$translateButtonPreference)

// toolbar
toolbarViewModel.style = kind == .conversationRoot ? .plain : .inline
}
}
}
Expand All @@ -248,6 +234,7 @@ extension StatusView.ViewModel {
useParagraphMark: true
)
self.content = metaContent
self.contentAttributedString = metaContent.attributedString(accentColor: .tintColor)
// delegate?.statusView(self, translateContentDidChange: status)
} catch {
debugPrint(error.localizedDescription)
Expand Down Expand Up @@ -503,6 +490,7 @@ extension StatusView.ViewModel {
useParagraphMark: true
)
self.content = metaContent
self.contentAttributedString = metaContent.attributedString(accentColor: .tintColor)
self.isContentContainsMeta = false
}

Expand Down Expand Up @@ -653,8 +641,24 @@ extension StatusView.ViewModel {
.assign(to: &$protected)

// visibility
visibility = status.visibility

let _visibility = status.visibility
visibility = _visibility
visibilityIconImage = {
switch _visibility {
case .public:
return Asset.ObjectTools.globeMiniInline.image.withRenderingMode(.alwaysTemplate)
case .unlisted:
return Asset.ObjectTools.lockOpenMiniInline.image.withRenderingMode(.alwaysTemplate)
case .private:
return Asset.ObjectTools.lockMiniInline.image.withRenderingMode(.alwaysTemplate)
case .direct:
return Asset.Communication.mailMiniInline.image.withRenderingMode(.alwaysTemplate)
case ._other:
assertionFailure()
return nil
}
}()

// timestamp
timestampLabelViewModel = TimestampLabelView.ViewModel(timestamp: status.createdAt)

Expand All @@ -664,10 +668,12 @@ extension StatusView.ViewModel {
let content = MastodonContent(content: spoilerText, emojis: status.emojisTransient.asDictionary)
let metaContent = try MastodonMetaContent.convert(document: content, useParagraphMark: true)
self.spoilerContent = metaContent
self.spoilerContentAttributedString = metaContent.attributedString(accentColor: .tintColor)
self.isSpoilerContentContainsMeta = !status.emojisTransient.isEmpty
} catch {
assertionFailure(error.localizedDescription)
self.spoilerContent = nil
self.spoilerContentAttributedString = nil
}
}

Expand All @@ -676,10 +682,12 @@ extension StatusView.ViewModel {
let content = MastodonContent(content: status.content, emojis: status.emojisTransient.asDictionary)
let metaContent = try MastodonMetaContent.convert(document: content, useParagraphMark: true)
self.content = metaContent
self.contentAttributedString = metaContent.attributedString(accentColor: .tintColor)
self.isContentContainsMeta = !status.emojisTransient.isEmpty
} catch {
assertionFailure(error.localizedDescription)
self.content = PlaintextMetaContent(string: "")
self.contentAttributedString = AttributedString("")
}

// language
Expand Down Expand Up @@ -783,11 +791,13 @@ extension StatusView.ViewModel {
viewModel.avatarURL = URL(string: "https://pbs.twimg.com/profile_images/809741368134234112/htSiXXAU_400x400.jpg")
viewModel.authorName = PlaintextMetaContent(string: "Twidere")
viewModel.authorUsernme = "TwidereProject"
viewModel.content = TwitterMetaContent.convert(
let metaContent = TwitterMetaContent.convert(
document: TwitterContent(content: L10n.Scene.Settings.Display.Preview.thankForUsingTwidereX, urlEntities: []),
urlMaximumLength: 16,
twitterTextProvider: SwiftTwitterTextProvider()
)
viewModel.content = metaContent
viewModel.contentAttributedString = metaContent.attributedString(accentColor: .tintColor)

return viewModel
}
Expand Down
6 changes: 3 additions & 3 deletions TwidereSDK/Sources/TwidereUI/Content/StatusView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -449,8 +449,8 @@ extension StatusView {
// ignore tap
}
} else {
let metaContent = viewModel.spoilerContent ?? PlaintextMetaContent(string: "")
Text(metaContent.attributedString(accentColor: .tintColor))
let metaContent = viewModel.spoilerContentAttributedString ?? AttributedString("")
Text(metaContent)
.multilineTextAlignment(.leading)
.font(Font(TextStyle.statusContent.font))
.foregroundColor(Color(uiColor: TextStyle.statusContent.textColor))
Expand All @@ -474,7 +474,7 @@ extension StatusView {
// ignore tap
}
} else {
Text(viewModel.content.attributedString(accentColor: .tintColor))
Text(viewModel.contentAttributedString)
.multilineTextAlignment(.leading)
.font(Font(TextStyle.statusContent.font))
.foregroundColor(Color(uiColor: TextStyle.statusContent.textColor))
Expand Down
2 changes: 0 additions & 2 deletions TwidereX.xcodeproj/xcshareddata/xcschemes/TwidereX.xcscheme
Original file line number Diff line number Diff line change
Expand Up @@ -73,8 +73,6 @@
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
enableThreadSanitizer = "YES"
enableUBSanitizer = "YES"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
Expand Down

0 comments on commit ae34f79

Please sign in to comment.