From cfdd2ea6705c1ed07a3fd04895527375040d5387 Mon Sep 17 00:00:00 2001 From: sunxiaojian Date: Fri, 16 Apr 2021 21:55:09 +0800 Subject: [PATCH] chore: use stackView --- Mastodon.xcodeproj/project.pbxproj | 4 + Mastodon/Extension/UIView+Remove.swift | 18 +++ .../NotificationViewController.swift | 2 +- .../NotificationViewModel+diffable.swift | 4 +- .../NotificationStatusTableViewCell.swift | 130 ++++++++++++------ .../NotificationTableViewCell.swift | 92 +++++++++---- 6 files changed, 176 insertions(+), 74 deletions(-) create mode 100644 Mastodon/Extension/UIView+Remove.swift diff --git a/Mastodon.xcodeproj/project.pbxproj b/Mastodon.xcodeproj/project.pbxproj index 3244dcd3..19506bb9 100644 --- a/Mastodon.xcodeproj/project.pbxproj +++ b/Mastodon.xcodeproj/project.pbxproj @@ -136,6 +136,7 @@ 5D0393902612D259007FE196 /* WebViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5D03938F2612D259007FE196 /* WebViewController.swift */; }; 5D0393962612D266007FE196 /* WebViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5D0393952612D266007FE196 /* WebViewModel.swift */; }; 5D526FE225BE9AC400460CB9 /* MastodonSDK in Frameworks */ = {isa = PBXBuildFile; productRef = 5D526FE125BE9AC400460CB9 /* MastodonSDK */; }; + 5DA732CC2629CEF500A92342 /* UIView+Remove.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5DA732CB2629CEF500A92342 /* UIView+Remove.swift */; }; 5DDDF1932617442700311060 /* Mastodon+Entity+Account.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5DDDF1922617442700311060 /* Mastodon+Entity+Account.swift */; }; 5DDDF1992617447F00311060 /* Mastodon+Entity+Tag.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5DDDF1982617447F00311060 /* Mastodon+Entity+Tag.swift */; }; 5DDDF1A92617489F00311060 /* Mastodon+Entity+History.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5DDDF1A82617489F00311060 /* Mastodon+Entity+History.swift */; }; @@ -525,6 +526,7 @@ 459EA4F43058CAB47719E963 /* Pods-Mastodon-MastodonUITests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Mastodon-MastodonUITests.debug.xcconfig"; path = "Target Support Files/Pods-Mastodon-MastodonUITests/Pods-Mastodon-MastodonUITests.debug.xcconfig"; sourceTree = ""; }; 5D03938F2612D259007FE196 /* WebViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebViewController.swift; sourceTree = ""; }; 5D0393952612D266007FE196 /* WebViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebViewModel.swift; sourceTree = ""; }; + 5DA732CB2629CEF500A92342 /* UIView+Remove.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIView+Remove.swift"; sourceTree = ""; }; 5DDDF1922617442700311060 /* Mastodon+Entity+Account.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Mastodon+Entity+Account.swift"; sourceTree = ""; }; 5DDDF1982617447F00311060 /* Mastodon+Entity+Tag.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Mastodon+Entity+Tag.swift"; sourceTree = ""; }; 5DDDF1A82617489F00311060 /* Mastodon+Entity+History.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Mastodon+Entity+History.swift"; sourceTree = ""; }; @@ -1614,6 +1616,7 @@ 5DF1056325F887CB00D6C0D4 /* AVPlayer.swift */, DB47229625F9EFAD00DA7F53 /* NSManagedObjectContext.swift */, 2D32EAB925CB9B0500C9ED86 /* UIView.swift */, + 5DA732CB2629CEF500A92342 /* UIView+Remove.swift */, 2DFF41882614A4DC00F776A4 /* UIView+Constraint.swift */, DB8AF55C25C138B7002E6C99 /* UIViewController.swift */, 2D24E1222626ED9D00A59D4F /* UIView+Gesture.swift */, @@ -2329,6 +2332,7 @@ DB71FD3C25F8A1C500512AE1 /* APIService+Persist+PersistCache.swift in Sources */, 2DA6055125F74407006356F9 /* AudioContainerViewModel.swift in Sources */, 0FB3D2FE25E4CB6400AAD544 /* PickServerTitleCell.swift in Sources */, + 5DA732CC2629CEF500A92342 /* UIView+Remove.swift in Sources */, 2D38F1DF25CD46A400561493 /* HomeTimelineViewController+StatusProvider.swift in Sources */, 2D206B9225F60EA700143C56 /* UIControl.swift in Sources */, 2D46975E25C2A54100CF4AA9 /* NSLayoutConstraint.swift in Sources */, diff --git a/Mastodon/Extension/UIView+Remove.swift b/Mastodon/Extension/UIView+Remove.swift new file mode 100644 index 00000000..473b3c34 --- /dev/null +++ b/Mastodon/Extension/UIView+Remove.swift @@ -0,0 +1,18 @@ +// +// UIView+Remove.swift +// Mastodon +// +// Created by xiaojian sun on 2021/4/16. +// + +import Foundation +import UIKit + +extension UIView { + func removeFromStackView() { + if let stackView = self.superview as? UIStackView { + stackView.removeArrangedSubview(self) + } + self.removeFromSuperview() + } +} diff --git a/Mastodon/Scene/Notification/NotificationViewController.swift b/Mastodon/Scene/Notification/NotificationViewController.swift index ddc997a5..3fb86259 100644 --- a/Mastodon/Scene/Notification/NotificationViewController.swift +++ b/Mastodon/Scene/Notification/NotificationViewController.swift @@ -35,7 +35,7 @@ final class NotificationViewController: UIViewController, NeedsDependency { tableView.register(NotificationStatusTableViewCell.self, forCellReuseIdentifier: String(describing: NotificationStatusTableViewCell.self)) tableView.register(TimelineBottomLoaderTableViewCell.self, forCellReuseIdentifier: String(describing: TimelineBottomLoaderTableViewCell.self)) tableView.tableFooterView = UIView() - tableView.rowHeight = UITableView.automaticDimension + tableView.estimatedRowHeight = UITableView.automaticDimension return tableView }() diff --git a/Mastodon/Scene/Notification/NotificationViewModel+diffable.swift b/Mastodon/Scene/Notification/NotificationViewModel+diffable.swift index 774620f8..c65b5096 100644 --- a/Mastodon/Scene/Notification/NotificationViewModel+diffable.swift +++ b/Mastodon/Scene/Notification/NotificationViewModel+diffable.swift @@ -74,9 +74,7 @@ extension NotificationViewModel: NSFetchedResultsControllerDelegate { newSnapshot.appendItems([.bottomLoader], toSection: .main) } guard let difference = self.calculateReloadSnapshotDifference(navigationBar: navigationBar, tableView: tableView, oldSnapshot: oldSnapshot, newSnapshot: newSnapshot) else { - diffableDataSource.apply(newSnapshot, animatingDifferences: false) { - tableView.reloadData() - } + diffableDataSource.apply(newSnapshot, animatingDifferences: false) self.isFetchingLatestNotification.value = false return } diff --git a/Mastodon/Scene/Notification/TableViewCell/NotificationStatusTableViewCell.swift b/Mastodon/Scene/Notification/TableViewCell/NotificationStatusTableViewCell.swift index dc3f49bb..5d1a14be 100644 --- a/Mastodon/Scene/Notification/TableViewCell/NotificationStatusTableViewCell.swift +++ b/Mastodon/Scene/Notification/TableViewCell/NotificationStatusTableViewCell.swift @@ -11,7 +11,6 @@ import UIKit final class NotificationStatusTableViewCell: UITableViewCell { static let actionImageBorderWidth: CGFloat = 2 - static let statusPadding = UIEdgeInsets(top: 50, left: 73, bottom: 24, right: 24) var disposeBag = Set() var pollCountdownSubscription: AnyCancellable? @@ -42,6 +41,11 @@ final class NotificationStatusTableViewCell: UITableViewCell { return view }() + let avatarContainer: UIView = { + let view = UIView() + return view + }() + let actionLabel: UILabel = { let label = UILabel() label.textColor = Asset.Colors.Label.secondary.color @@ -103,53 +107,99 @@ final class NotificationStatusTableViewCell: UITableViewCell { extension NotificationStatusTableViewCell { func configure() { - - let container = UIView() - container.backgroundColor = .clear - contentView.addSubview(container) - container.constrain([ - container.topAnchor.constraint(equalTo: contentView.topAnchor), - container.leadingAnchor.constraint(equalTo: contentView.readableContentGuide.leadingAnchor), - container.trailingAnchor.constraint(equalTo: contentView.readableContentGuide.trailingAnchor), - container.bottomAnchor.constraint(equalTo: contentView.bottomAnchor) + let containerStackView = UIStackView() + containerStackView.axis = .horizontal + containerStackView.alignment = .top + containerStackView.spacing = 4 + containerStackView.layoutMargins = UIEdgeInsets(top: 14, left: 0, bottom: 12, right: 0) + containerStackView.isLayoutMarginsRelativeArrangement = true + containerStackView.translatesAutoresizingMaskIntoConstraints = false + contentView.addSubview(containerStackView) + NSLayoutConstraint.activate([ + containerStackView.topAnchor.constraint(equalTo: contentView.topAnchor), + containerStackView.leadingAnchor.constraint(equalTo: contentView.readableContentGuide.leadingAnchor), + contentView.readableContentGuide.trailingAnchor.constraint(equalTo: containerStackView.trailingAnchor), + contentView.bottomAnchor.constraint(equalTo: containerStackView.bottomAnchor), ]) - - container.addSubview(avatatImageView) - avatatImageView.pin(toSize: CGSize(width: 35, height: 35)) - avatatImageView.pin(top: 12, left: 0, bottom: nil, right: nil) - - container.addSubview(actionImageBackground) - actionImageBackground.pin(toSize: CGSize(width: 24 + NotificationTableViewCell.actionImageBorderWidth, height: 24 + NotificationTableViewCell.actionImageBorderWidth)) - actionImageBackground.pin(top: 33, left: 21, bottom: nil, right: nil) - - actionImageBackground.addSubview(actionImageView) - actionImageView.constrainToCenter() - container.addSubview(nameLabel) - nameLabel.constrain([ - nameLabel.topAnchor.constraint(equalTo: container.topAnchor, constant: 12), - nameLabel.leadingAnchor.constraint(equalTo: container.leadingAnchor, constant: 61) + containerStackView.addArrangedSubview(avatarContainer) + avatarContainer.translatesAutoresizingMaskIntoConstraints = false + NSLayoutConstraint.activate([ + avatarContainer.heightAnchor.constraint(equalToConstant: 47).priority(.required - 1), + avatarContainer.widthAnchor.constraint(equalToConstant: 47).priority(.required - 1) + ]) + + avatarContainer.addSubview(avatatImageView) + avatatImageView.translatesAutoresizingMaskIntoConstraints = false + NSLayoutConstraint.activate([ + avatatImageView.heightAnchor.constraint(equalToConstant: 35).priority(.required - 1), + avatatImageView.widthAnchor.constraint(equalToConstant: 35).priority(.required - 1), + avatatImageView.topAnchor.constraint(equalTo: avatarContainer.topAnchor), + avatatImageView.leadingAnchor.constraint(equalTo: avatarContainer.leadingAnchor) + ]) + + avatarContainer.addSubview(actionImageBackground) + actionImageBackground.translatesAutoresizingMaskIntoConstraints = false + NSLayoutConstraint.activate([ + actionImageBackground.heightAnchor.constraint(equalToConstant: 24 + NotificationTableViewCell.actionImageBorderWidth).priority(.required - 1), + actionImageBackground.widthAnchor.constraint(equalToConstant: 24 + NotificationTableViewCell.actionImageBorderWidth).priority(.required - 1), + actionImageBackground.bottomAnchor.constraint(equalTo: avatarContainer.bottomAnchor), + actionImageBackground.trailingAnchor.constraint(equalTo: avatarContainer.trailingAnchor) + ]) + + avatarContainer.addSubview(actionImageView) + actionImageView.translatesAutoresizingMaskIntoConstraints = false + NSLayoutConstraint.activate([ + actionImageView.centerXAnchor.constraint(equalTo: actionImageBackground.centerXAnchor), + actionImageView.centerYAnchor.constraint(equalTo: actionImageBackground.centerYAnchor) + ]) + + + let actionStackView = UIStackView() + actionStackView.axis = .horizontal + actionStackView.distribution = .fillProportionally + actionStackView.spacing = 4 + actionStackView.translatesAutoresizingMaskIntoConstraints = false + + nameLabel.translatesAutoresizingMaskIntoConstraints = false + actionStackView.addArrangedSubview(nameLabel) + actionLabel.translatesAutoresizingMaskIntoConstraints = false + actionStackView.addArrangedSubview(actionLabel) + nameLabel.setContentCompressionResistancePriority(.required - 1, for: .vertical) + + let statusStackView = UIStackView() + statusStackView.axis = .vertical + + statusStackView.distribution = .fill + statusStackView.spacing = 4 + statusStackView.translatesAutoresizingMaskIntoConstraints = false + statusView.translatesAutoresizingMaskIntoConstraints = false + statusStackView.addArrangedSubview(actionStackView) + + statusBorder.translatesAutoresizingMaskIntoConstraints = false + statusView.translatesAutoresizingMaskIntoConstraints = false + statusBorder.addSubview(statusView) + NSLayoutConstraint.activate([ + statusView.topAnchor.constraint(equalTo: statusBorder.topAnchor, constant: 12), + statusView.leadingAnchor.constraint(equalTo: statusBorder.leadingAnchor, constant: 12), + statusBorder.bottomAnchor.constraint(equalTo: statusView.bottomAnchor, constant: 12), + statusBorder.trailingAnchor.constraint(equalTo: statusView.trailingAnchor, constant: 12), ]) - container.addSubview(actionLabel) - actionLabel.constrain([ - actionLabel.leadingAnchor.constraint(equalTo: nameLabel.trailingAnchor, constant: 4), - actionLabel.centerYAnchor.constraint(equalTo: nameLabel.centerYAnchor), - container.trailingAnchor.constraint(greaterThanOrEqualTo: actionLabel.trailingAnchor, constant: 4) - ]) + + statusStackView.addArrangedSubview(statusBorder) + + containerStackView.addArrangedSubview(statusStackView) statusView.contentWarningBlurContentImageView.backgroundColor = Asset.Colors.Background.secondaryGroupedSystemBackground.color statusView.isUserInteractionEnabled = false // remove item don't display - statusView.actionToolbarContainer.isHidden = true - statusView.avatarView.isHidden = true - statusView.usernameLabel.isHidden = true - - container.addSubview(statusBorder) - statusBorder.pin(top: 40, left: 63, bottom: 14, right: 14) - - container.addSubview(statusView) - statusView.pin(top: NotificationStatusTableViewCell.statusPadding.top, left: NotificationStatusTableViewCell.statusPadding.left, bottom: NotificationStatusTableViewCell.statusPadding.bottom, right: NotificationStatusTableViewCell.statusPadding.right) + statusView.actionToolbarContainer.removeFromStackView() + // it affect stackView's height + statusView.avatarView.removeFromStackView() + statusView.usernameLabel.removeFromStackView() + statusView.nameLabel.setContentCompressionResistancePriority(.required - 1, for: .vertical) + statusView.activeTextLabel.setContentCompressionResistancePriority(.required, for: .vertical) } override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) { diff --git a/Mastodon/Scene/Notification/TableViewCell/NotificationTableViewCell.swift b/Mastodon/Scene/Notification/TableViewCell/NotificationTableViewCell.swift index cda4d75d..1b7560a7 100644 --- a/Mastodon/Scene/Notification/TableViewCell/NotificationTableViewCell.swift +++ b/Mastodon/Scene/Notification/TableViewCell/NotificationTableViewCell.swift @@ -50,6 +50,11 @@ final class NotificationTableViewCell: UITableViewCell { return view }() + let avatarContainer: UIView = { + let view = UIView() + return view + }() + let actionLabel: UILabel = { let label = UILabel() label.textColor = Asset.Colors.Label.secondary.color @@ -86,40 +91,67 @@ final class NotificationTableViewCell: UITableViewCell { extension NotificationTableViewCell { func configure() { - let container = UIView() - container.backgroundColor = .clear - contentView.addSubview(container) - container.constrain([ - container.topAnchor.constraint(equalTo: contentView.topAnchor), - container.leadingAnchor.constraint(equalTo: contentView.readableContentGuide.leadingAnchor), - container.trailingAnchor.constraint(equalTo: contentView.readableContentGuide.trailingAnchor), - container.bottomAnchor.constraint(equalTo: contentView.bottomAnchor) + let containerStackView = UIStackView() + containerStackView.axis = .horizontal + containerStackView.alignment = .center + containerStackView.spacing = 4 + containerStackView.layoutMargins = UIEdgeInsets(top: 14, left: 0, bottom: 12, right: 0) + containerStackView.isLayoutMarginsRelativeArrangement = true + containerStackView.translatesAutoresizingMaskIntoConstraints = false + contentView.addSubview(containerStackView) + NSLayoutConstraint.activate([ + containerStackView.topAnchor.constraint(equalTo: contentView.topAnchor), + containerStackView.leadingAnchor.constraint(equalTo: contentView.readableContentGuide.leadingAnchor), + contentView.readableContentGuide.trailingAnchor.constraint(equalTo: containerStackView.trailingAnchor), + contentView.bottomAnchor.constraint(equalTo: containerStackView.bottomAnchor), ]) - - container.addSubview(avatatImageView) - avatatImageView.pin(toSize: CGSize(width: 35, height: 35)) - avatatImageView.pin(top: 12, left: 0, bottom: nil, right: nil) - - container.addSubview(actionImageBackground) - actionImageBackground.pin(toSize: CGSize(width: 24 + NotificationTableViewCell.actionImageBorderWidth, height: 24 + NotificationTableViewCell.actionImageBorderWidth)) - actionImageBackground.pin(top: 33, left: 33, bottom: nil, right: nil) - - actionImageBackground.addSubview(actionImageView) - actionImageView.constrainToCenter() - container.addSubview(nameLabel) - nameLabel.constrain([ - nameLabel.topAnchor.constraint(equalTo: container.topAnchor, constant: 24), - container.bottomAnchor.constraint(equalTo: nameLabel.bottomAnchor, constant: 24), - nameLabel.leadingAnchor.constraint(equalTo: container.leadingAnchor, constant: 61) + containerStackView.addArrangedSubview(avatarContainer) + avatarContainer.translatesAutoresizingMaskIntoConstraints = false + NSLayoutConstraint.activate([ + avatarContainer.heightAnchor.constraint(equalToConstant: 47).priority(.required - 1), + avatarContainer.widthAnchor.constraint(equalToConstant: 47).priority(.required - 1) ]) + + avatarContainer.addSubview(avatatImageView) + avatatImageView.translatesAutoresizingMaskIntoConstraints = false + NSLayoutConstraint.activate([ + avatatImageView.heightAnchor.constraint(equalToConstant: 35).priority(.required - 1), + avatatImageView.widthAnchor.constraint(equalToConstant: 35).priority(.required - 1), + avatatImageView.topAnchor.constraint(equalTo: avatarContainer.topAnchor), + avatatImageView.leadingAnchor.constraint(equalTo: avatarContainer.leadingAnchor) + ]) + + avatarContainer.addSubview(actionImageBackground) + actionImageBackground.translatesAutoresizingMaskIntoConstraints = false + NSLayoutConstraint.activate([ + actionImageBackground.heightAnchor.constraint(equalToConstant: 24 + NotificationTableViewCell.actionImageBorderWidth).priority(.required - 1), + actionImageBackground.widthAnchor.constraint(equalToConstant: 24 + NotificationTableViewCell.actionImageBorderWidth).priority(.required - 1), + actionImageBackground.bottomAnchor.constraint(equalTo: avatarContainer.bottomAnchor), + actionImageBackground.trailingAnchor.constraint(equalTo: avatarContainer.trailingAnchor) + ]) + + avatarContainer.addSubview(actionImageView) + actionImageView.translatesAutoresizingMaskIntoConstraints = false + NSLayoutConstraint.activate([ + actionImageView.centerXAnchor.constraint(equalTo: actionImageBackground.centerXAnchor), + actionImageView.centerYAnchor.constraint(equalTo: actionImageBackground.centerYAnchor) + ]) + + + let actionStackView = UIStackView() + actionStackView.axis = .horizontal + actionStackView.distribution = .fillProportionally + actionStackView.spacing = 4 + actionStackView.translatesAutoresizingMaskIntoConstraints = false - container.addSubview(actionLabel) - actionLabel.constrain([ - actionLabel.leadingAnchor.constraint(equalTo: nameLabel.trailingAnchor, constant: 4), - actionLabel.centerYAnchor.constraint(equalTo: nameLabel.centerYAnchor), - container.trailingAnchor.constraint(greaterThanOrEqualTo: actionLabel.trailingAnchor, constant: 4) - ]) + nameLabel.translatesAutoresizingMaskIntoConstraints = false + containerStackView.addArrangedSubview(nameLabel) + actionLabel.translatesAutoresizingMaskIntoConstraints = false + containerStackView.addArrangedSubview(actionLabel) + nameLabel.setContentCompressionResistancePriority(.required - 1, for: .vertical) + containerStackView.addArrangedSubview(actionStackView) + } override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {