diff --git a/RELEASE-NOTES.txt b/RELEASE-NOTES.txt index c666a933014a..93292199560f 100644 --- a/RELEASE-NOTES.txt +++ b/RELEASE-NOTES.txt @@ -1,5 +1,7 @@ 25.2 ----- + +* [*] [Jetpack-only] Stats: Made optimizations to enhance scrolling performance. [#22847] * [*] Simplify post list context menu sections [#23356] * [*] Fix an issue with incorrect snackbar shown when saving drafts manually [#23358] diff --git a/WordPress/Classes/ViewRelated/Stats/Extensions/UITableViewCell+Stats.swift b/WordPress/Classes/ViewRelated/Stats/Extensions/UITableViewCell+Stats.swift index be1b8a00e80f..4f7e3363a16d 100644 --- a/WordPress/Classes/ViewRelated/Stats/Extensions/UITableViewCell+Stats.swift +++ b/WordPress/Classes/ViewRelated/Stats/Extensions/UITableViewCell+Stats.swift @@ -70,5 +70,4 @@ extension UITableViewCell { row.configure(statSection: statSection, delegate: delegate) rowsStackView.addArrangedSubview(row) } - } diff --git a/WordPress/Classes/ViewRelated/Stats/Extensions/WPStyleGuide+Stats.swift b/WordPress/Classes/ViewRelated/Stats/Extensions/WPStyleGuide+Stats.swift index a3498ecd2148..dc176cc818f2 100644 --- a/WordPress/Classes/ViewRelated/Stats/Extensions/WPStyleGuide+Stats.swift +++ b/WordPress/Classes/ViewRelated/Stats/Extensions/WPStyleGuide+Stats.swift @@ -73,7 +73,7 @@ extension WPStyleGuide { label.textColor = .DS.Foreground.secondary label.font = .DS.font(.footnote) label.adjustsFontSizeToFitWidth = true - label.maximumContentSizeCategory = .accessibilityLarge + label.maximumContentSizeCategory = .extraExtraExtraLarge } static func configureLabelAsLink(_ label: UILabel) { diff --git a/WordPress/Classes/ViewRelated/Stats/Period Stats/Countries/CountriesCell.swift b/WordPress/Classes/ViewRelated/Stats/Period Stats/Countries/CountriesCell.swift index e0324c0c65ce..ea6ac16fc325 100644 --- a/WordPress/Classes/ViewRelated/Stats/Period Stats/Countries/CountriesCell.swift +++ b/WordPress/Classes/ViewRelated/Stats/Period Stats/Countries/CountriesCell.swift @@ -1,12 +1,11 @@ import UIKit -class CountriesCell: StatsBaseCell, NibLoadable { +final class CountriesCell: StatsRowsCell, NibLoadable { // MARK: - Properties @IBOutlet weak var topSeparatorLine: UIView! @IBOutlet weak var subtitleStackView: UIStackView! - @IBOutlet weak var rowsStackView: UIStackView! @IBOutlet weak var itemSubtitleLabel: UILabel! @IBOutlet weak var dataSubtitleLabel: UILabel! @IBOutlet weak var bottomSeparatorLine: UIView! @@ -38,21 +37,24 @@ class CountriesCell: StatsBaseCell, NibLoadable { bottomSeparatorLine.isHidden = forDetails if !forDetails { - addRows(dataRows, - toStackView: rowsStackView, + configureTotalRows( + dataRows, + inStackView: rowsStackView, forType: .period, - limitRowsDisplayed: true, - viewMoreDelegate: self) + configuration: .init( + limitRowsDisplayed: true, + rowDelegate: nil, + referrerDelegate: nil, + viewMoreDelegate: self + ) + ) + } else { + removeRowsFromStackView(rowsStackView) } setSubtitleVisibility() applyStyles() } - - override func prepareForReuse() { - super.prepareForReuse() - removeRowsFromStackView(rowsStackView) - } } private extension CountriesCell { @@ -66,7 +68,6 @@ private extension CountriesCell { } func setSubtitleVisibility() { - subtitleStackView.layoutIfNeeded() let subtitleHeight = subtitlesStackViewTopConstraint.constant * 2 + subtitleStackView.frame.height if forDetails { diff --git a/WordPress/Classes/ViewRelated/Stats/Shared Views/Stats Detail/StatsRowsCell.swift b/WordPress/Classes/ViewRelated/Stats/Shared Views/Stats Detail/StatsRowsCell.swift new file mode 100644 index 000000000000..43852efc6614 --- /dev/null +++ b/WordPress/Classes/ViewRelated/Stats/Shared Views/Stats Detail/StatsRowsCell.swift @@ -0,0 +1,93 @@ +import Foundation + +class StatsRowsCell: StatsBaseCell { + struct StatsTotalRowConfiguration { + let limitRowsDisplayed: Bool + let rowDelegate: StatsTotalRowDelegate? + let referrerDelegate: StatsTotalRowReferrerDelegate? + let viewMoreDelegate: ViewMoreRowDelegate? + } + + @IBOutlet weak var rowsStackView: UIStackView! + + override func awakeFromNib() { + super.awakeFromNib() + + addDefaultTotalRows(toStackView: rowsStackView) + } + + func configureTotalRows(_ dataRows: [StatsTotalRowData], + inStackView rowsStackView: UIStackView, + forType statType: StatType, + configuration: StatsTotalRowConfiguration) { + if rowsStackView.arrangedSubviews.isEmpty { + addDefaultTotalRows(toStackView: rowsStackView) + } + + guard !dataRows.isEmpty else { + configureForNoData(inStackView: rowsStackView, forType: statType) + return + } + + let numberOfRowsToAdd = calculateNumberOfRowsToAdd(from: dataRows, withConfiguration: configuration) + + rowsStackView.arrangedSubviews.enumerated().forEach { index, view in + configure(view: view, at: index, in: dataRows, numberOfRowsToAdd: numberOfRowsToAdd, configuration: configuration) + } + } + + private func addDefaultTotalRows(toStackView rowsStackView: UIStackView) { + for _ in 0.. Int { + if configuration.limitRowsDisplayed { + return min(dataRows.count, StatsDataHelper.maxRowsToDisplay) + } + return dataRows.count + } + + private func configure(view: UIView, at index: Int, in dataRows: [StatsTotalRowData], numberOfRowsToAdd: Int, configuration: StatsTotalRowConfiguration) { + switch view { + case let view as StatsNoDataRow: + view.isHidden = true + case let view as ViewMoreRow: + configureViewMoreRow(view, at: index, in: dataRows, withConfiguration: configuration) + case let view as StatsTotalRow where index < dataRows.count: + view.isHidden = false + let dataRow = dataRows[index] + view.configure(rowData: dataRow, delegate: configuration.rowDelegate, referrerDelegate: configuration.referrerDelegate) + view.showSeparator = index != (numberOfRowsToAdd - 1) + default: + view.isHidden = true + } + } + + private func configureViewMoreRow(_ viewMoreRow: ViewMoreRow, at index: Int, in dataRows: [StatsTotalRowData], withConfiguration configuration: StatsTotalRowConfiguration) { + let shouldShowViewMore = configuration.limitRowsDisplayed && dataRows.count > StatsDataHelper.maxRowsToDisplay + viewMoreRow.isHidden = !shouldShowViewMore + if shouldShowViewMore { + viewMoreRow.configure(statSection: dataRows.first?.statSection, delegate: configuration.viewMoreDelegate) + } + } +} diff --git a/WordPress/Classes/ViewRelated/Stats/Shared Views/TopTotalsCell.swift b/WordPress/Classes/ViewRelated/Stats/Shared Views/TopTotalsCell.swift index 9a9bf1c63eb0..669d11678739 100644 --- a/WordPress/Classes/ViewRelated/Stats/Shared Views/TopTotalsCell.swift +++ b/WordPress/Classes/ViewRelated/Stats/Shared Views/TopTotalsCell.swift @@ -7,13 +7,12 @@ import UIKit /// If the row has child rows, those child rows are added to the stack view below the selected row. /// -class TopTotalsCell: StatsBaseCell, NibLoadable { +final class TopTotalsCell: StatsRowsCell, NibLoadable { // MARK: - Properties @IBOutlet weak var outerStackView: UIStackView! @IBOutlet weak var subtitleStackView: UIStackView! - @IBOutlet weak var rowsStackView: UIStackView! @IBOutlet weak var itemSubtitleLabel: UILabel! @IBOutlet weak var dataSubtitleLabel: UILabel! @IBOutlet weak var dataSubtitleLabelWidthConstraint: NSLayoutConstraint! @@ -77,15 +76,20 @@ class TopTotalsCell: StatsBaseCell, NibLoadable { self.forDetails = forDetails if !forDetails { - addRows(dataRows, - toStackView: rowsStackView, - forType: siteStatsPeriodDelegate != nil ? .period : .insights, + configureTotalRows( + dataRows, + inStackView: rowsStackView, + forType: siteStatsPeriodDelegate != nil ? .period : .insights, + configuration: .init( limitRowsDisplayed: limitRowsDisplayed, rowDelegate: self, referrerDelegate: self, - viewMoreDelegate: self) - + viewMoreDelegate: self + ) + ) initChildRows() + } else { + rowsStackView.arrangedSubviews.forEach { $0.removeFromSuperview() } } setSubtitleVisibility() @@ -93,25 +97,6 @@ class TopTotalsCell: StatsBaseCell, NibLoadable { prepareForVoiceOver() } - override func prepareForReuse() { - super.prepareForReuse() - - rowsStackView.arrangedSubviews.forEach { subview in - - // Remove granchild rows - if let row = subview as? StatsTotalRow { - removeChildRowsForRow(row) - } - - // Remove child rows - if let childView = subview as? StatsChildRowsView { - removeRowsFromStackView(childView.rowsStackView) - } - } - - removeRowsFromStackView(rowsStackView) - } - private enum Metrics { static let topAccessoryViewSpacing: CGFloat = 32.0 } @@ -136,8 +121,6 @@ private extension TopTotalsCell { /// - Hide the stack view. /// func setSubtitleVisibility() { - subtitleStackView.layoutIfNeeded() - if forDetails { bottomSeparatorLine.isHidden = true diff --git a/WordPress/WordPress.xcodeproj/project.pbxproj b/WordPress/WordPress.xcodeproj/project.pbxproj index c804b043713a..b8e947af21d5 100644 --- a/WordPress/WordPress.xcodeproj/project.pbxproj +++ b/WordPress/WordPress.xcodeproj/project.pbxproj @@ -255,6 +255,8 @@ 01E70EBC2BB5CCCF000BFE45 /* NumberFormatter+Stats.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01E70EBA2BB5CCCF000BFE45 /* NumberFormatter+Stats.swift */; }; 01E70EBD2BB5D035000BFE45 /* NumberFormatter+Stats.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01E70EBA2BB5CCCF000BFE45 /* NumberFormatter+Stats.swift */; }; 01E78D1D296EA54F00FB6863 /* StatsPeriodHelperTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01E78D1C296EA54F00FB6863 /* StatsPeriodHelperTests.swift */; }; + 01FB42F42C25651000F5069E /* StatsRowsCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01FB42F32C25651000F5069E /* StatsRowsCell.swift */; }; + 01FB42F52C25651000F5069E /* StatsRowsCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01FB42F32C25651000F5069E /* StatsRowsCell.swift */; }; 02761EC02270072F009BAF0F /* BlogDetailsViewController+SectionHelpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02761EBF2270072F009BAF0F /* BlogDetailsViewController+SectionHelpers.swift */; }; 02761EC222700A9C009BAF0F /* BlogDetailsSubsectionToSectionCategoryTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02761EC122700A9C009BAF0F /* BlogDetailsSubsectionToSectionCategoryTests.swift */; }; 02761EC4227010BC009BAF0F /* BlogDetailsSectionIndexTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02761EC3227010BC009BAF0F /* BlogDetailsSectionIndexTests.swift */; }; @@ -6056,6 +6058,7 @@ 01E2580D2ACDC88100F09666 /* PlanWizardContentViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlanWizardContentViewModelTests.swift; sourceTree = ""; }; 01E70EBA2BB5CCCF000BFE45 /* NumberFormatter+Stats.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NumberFormatter+Stats.swift"; sourceTree = ""; }; 01E78D1C296EA54F00FB6863 /* StatsPeriodHelperTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatsPeriodHelperTests.swift; sourceTree = ""; }; + 01FB42F32C25651000F5069E /* StatsRowsCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatsRowsCell.swift; sourceTree = ""; }; 02761EBF2270072F009BAF0F /* BlogDetailsViewController+SectionHelpers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "BlogDetailsViewController+SectionHelpers.swift"; sourceTree = ""; }; 02761EC122700A9C009BAF0F /* BlogDetailsSubsectionToSectionCategoryTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlogDetailsSubsectionToSectionCategoryTests.swift; sourceTree = ""; }; 02761EC3227010BC009BAF0F /* BlogDetailsSectionIndexTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlogDetailsSectionIndexTests.swift; sourceTree = ""; }; @@ -14659,6 +14662,7 @@ 987535622282682D001661B4 /* DetailDataCell.xib */, DC9AF768285DF8A300EA2A0D /* StatsFollowersChartViewModel.swift */, 1762B6DB2845510400F270A5 /* StatsReferrersChartViewModel.swift */, + 01FB42F32C25651000F5069E /* StatsRowsCell.swift */, ); path = "Stats Detail"; sourceTree = ""; @@ -23344,6 +23348,7 @@ 01B7590B2B3ED63B00179AE6 /* DomainDetailsWebViewControllerWrapper.swift in Sources */, 0CB424F12ADEE52A0080B807 /* PostSearchToken.swift in Sources */, 98AA6D1126B8CE7200920C8B /* Comment+CoreDataClass.swift in Sources */, + 01FB42F42C25651000F5069E /* StatsRowsCell.swift in Sources */, 7E4A773720F802A8001C706D /* ActivityRangesFactory.swift in Sources */, E1AC282D18282423004D394C /* SFHFKeychainUtils.m in Sources */, 7EDAB3F420B046FE002D1A76 /* CircularProgressView+ActivityIndicatorType.swift in Sources */, @@ -25340,6 +25345,7 @@ F41E32FF287B47A500F89082 /* SuggestionsListViewModel.swift in Sources */, F4EDAA5129A795C600622D3D /* BlockedAuthor.swift in Sources */, FABB238E2602FC2C00C8785C /* NotificationSettingsService.swift in Sources */, + 01FB42F52C25651000F5069E /* StatsRowsCell.swift in Sources */, 019105872BE8BD6000CDFB16 /* StatsGhostSingleValueCell.swift in Sources */, 8091019429078CFE00FCB4EA /* JetpackFullscreenOverlayViewController.swift in Sources */, FA347AEE26EB6E300096604B /* GrowAudienceCell.swift in Sources */,