diff --git a/Signal/src/ViewControllers/ConversationView/CV/CVStackView.swift b/Signal/src/ViewControllers/ConversationView/CV/CVStackView.swift index 8f7ef60231..9aee6e454e 100644 --- a/Signal/src/ViewControllers/ConversationView/CV/CVStackView.swift +++ b/Signal/src/ViewControllers/ConversationView/CV/CVStackView.swift @@ -8,11 +8,11 @@ public typealias CVStackViewConfig = OWSStackView.Config // MARK: - -class CVStackView { +public extension UIStackView { - public static func measure(config: CVStackViewConfig, - subviewSizes: [CGSize], - verboseLogging: Bool = false) -> CGSize { + static func measure(config: CVStackViewConfig, + subviewSizes: [CGSize], + verboseLogging: Bool = false) -> CGSize { let spacingCount = max(0, subviewSizes.count - 1) @@ -56,6 +56,21 @@ class CVStackView { } return size } + + func apply(config: CVStackViewConfig) { + if self.axis != config.axis { + self.axis = config.axis + } + if self.alignment != config.alignment { + self.alignment = config.alignment + } + if self.spacing != config.spacing { + self.spacing = config.spacing + } + if self.layoutMargins != config.layoutMargins { + self.layoutMargins = config.layoutMargins + } + } } // MARK: - diff --git a/Signal/src/ViewControllers/MessageDetailViewController.swift b/Signal/src/ViewControllers/MessageDetailViewController.swift index f665d48f51..73878adaaa 100644 --- a/Signal/src/ViewControllers/MessageDetailViewController.swift +++ b/Signal/src/ViewControllers/MessageDetailViewController.swift @@ -380,25 +380,38 @@ class MessageDetailViewController: OWSTableViewController2 { private func buildAccessoryView(text: String, displayUDIndicator: Bool, - transaction: SDSAnyReadTransaction) -> UIView { - let label = UILabel() - label.textColor = Theme.ternaryTextColor - label.text = text + transaction: SDSAnyReadTransaction) -> ContactCellAccessoryView { + let label = CVLabel() label.textAlignment = .right - label.font = .ows_dynamicTypeFootnoteClamped + let labelConfig = CVLabelConfig(text: text, + font: .ows_dynamicTypeFootnoteClamped, + textColor: Theme.ternaryTextColor) + labelConfig.applyForRendering(label: label) + let labelSize = CVText.measureLabel(config: labelConfig, maxWidth: .greatestFiniteMagnitude) let shouldShowUD = preferences.shouldShowUnidentifiedDeliveryIndicators(transaction: transaction) - guard displayUDIndicator && shouldShowUD else { return label } + guard displayUDIndicator && shouldShowUD else { + return ContactCellAccessoryView(accessoryView: label, size: labelSize) + } - let imageView = UIImageView() + let imageView = CVImageView() imageView.setTemplateImageName(Theme.iconName(.sealedSenderIndicator), tintColor: Theme.ternaryTextColor) + let imageSize = CGSize.square(20) - let hStack = UIStackView(arrangedSubviews: [imageView, label]) - hStack.axis = .horizontal - hStack.spacing = 8 - - return hStack + let hStack = ManualStackView(name: "hStack") + let hStackConfig = CVStackViewConfig(axis: .horizontal, + alignment: .center, + spacing: 8, + layoutMargins: .zero) + let hStackMeasurement = hStack.configure(config: hStackConfig, + subviews: [imageView, label], + subviewInfos: [ + imageSize.asManualSubviewInfo(hasFixedSize: true), + labelSize.asManualSubviewInfo + ]) + let hStackSize = hStackMeasurement.measuredSize + return ContactCellAccessoryView(accessoryView: hStack, size: hStackSize) } private func buildValueLabel(name: String, value: String) -> UILabel { diff --git a/Signal/src/ViewControllers/NewGroupView/BaseGroupMemberViewController.swift b/Signal/src/ViewControllers/NewGroupView/BaseGroupMemberViewController.swift index 2cd1958ce0..771810c980 100644 --- a/Signal/src/ViewControllers/NewGroupView/BaseGroupMemberViewController.swift +++ b/Signal/src/ViewControllers/NewGroupView/BaseGroupMemberViewController.swift @@ -530,7 +530,7 @@ extension BaseGroupMemberViewController: RecipientPickerDelegate { func recipientPicker(_ recipientPickerViewController: RecipientPickerViewController, accessoryViewForRecipient recipient: PickedRecipient, - transaction: SDSAnyReadTransaction) -> UIView? { + transaction: SDSAnyReadTransaction) -> ContactCellAccessoryView? { guard let address = recipient.address else { owsFailDebug("Missing address.") return nil @@ -549,7 +549,7 @@ extension BaseGroupMemberViewController: RecipientPickerDelegate { let isPreExistingMember = groupMemberViewDelegate.groupMemberViewIsPreExistingMember(recipient, transaction: transaction) - let imageView = UIImageView() + let imageView = CVImageView() if isPreExistingMember { imageView.setTemplateImageName("check-circle-solid-24", tintColor: Theme.washColor) } else if isCurrentMember { @@ -560,17 +560,13 @@ extension BaseGroupMemberViewController: RecipientPickerDelegate { } else { imageView.setTemplateImageName("empty-circle-outline-24", tintColor: .ows_gray25) } - return imageView + return ContactCellAccessoryView(accessoryView: imageView, size: .square(24)) } func recipientPicker(_ recipientPickerViewController: RecipientPickerViewController, attributedSubtitleForRecipient recipient: PickedRecipient, transaction: SDSAnyReadTransaction) -> NSAttributedString? { - guard let groupMemberViewDelegate = groupMemberViewDelegate else { - owsFailDebug("Missing delegate.") - return nil - } guard let address = recipient.address else { owsFailDebug("Recipient missing address.") return nil diff --git a/Signal/src/ViewControllers/RecipientPicker/RecipientPickerDelegate.swift b/Signal/src/ViewControllers/RecipientPicker/RecipientPickerDelegate.swift index 4b49f4e656..ec9759f5d8 100644 --- a/Signal/src/ViewControllers/RecipientPicker/RecipientPickerDelegate.swift +++ b/Signal/src/ViewControllers/RecipientPicker/RecipientPickerDelegate.swift @@ -40,7 +40,7 @@ protocol RecipientPickerDelegate: AnyObject { @objc optional func recipientPicker(_ recipientPickerViewController: RecipientPickerViewController, accessoryViewForRecipient recipient: PickedRecipient, - transaction: SDSAnyReadTransaction) -> UIView? + transaction: SDSAnyReadTransaction) -> ContactCellAccessoryView? @objc optional func recipientPicker(_ recipientPickerViewController: RecipientPickerViewController, @@ -172,7 +172,7 @@ extension RecipientPickerViewController { func item(forRecipient recipient: PickedRecipient) -> OWSTableItem { let tableView = self.tableView - + switch recipient.identifier { case .address(let address): return OWSTableItem( diff --git a/Signal/src/ViewControllers/SafetyNumberConfirmationSheet.swift b/Signal/src/ViewControllers/SafetyNumberConfirmationSheet.swift index a748fb6797..244711c180 100644 --- a/Signal/src/ViewControllers/SafetyNumberConfirmationSheet.swift +++ b/Signal/src/ViewControllers/SafetyNumberConfirmationSheet.swift @@ -510,7 +510,10 @@ private class SafetyNumberCell: ContactTableViewCell { configuration.forceDarkAppearance = (theme == .translucentDark) - configuration.accessoryView = button + let buttonSize = button.sizeThatFits(.square(.greatestFiniteMagnitude)) + let buttonWrapper = ManualLayoutView.wrapSubviewUsingIOSAutoLayout(button) + configuration.accessoryView = ContactCellAccessoryView(accessoryView: buttonWrapper, + size: buttonSize) if let verificationState = item.verificationState, verificationState == .noLongerVerified { let previouslyVerified = NSMutableAttributedString() diff --git a/Signal/src/ViewControllers/ThreadSettings/GroupMemberRequestsAndInvitesViewController.swift b/Signal/src/ViewControllers/ThreadSettings/GroupMemberRequestsAndInvitesViewController.swift index 7f6f6c9241..6fc783703b 100644 --- a/Signal/src/ViewControllers/ThreadSettings/GroupMemberRequestsAndInvitesViewController.swift +++ b/Signal/src/ViewControllers/ThreadSettings/GroupMemberRequestsAndInvitesViewController.swift @@ -181,11 +181,10 @@ public class GroupMemberRequestsAndInvitesViewController: OWSTableViewController contents.addSection(section) } - private func buildMemberRequestButtons(address: SignalServiceAddress) -> UIView { + private func buildMemberRequestButtons(address: SignalServiceAddress) -> ContactCellAccessoryView { let buttonHeight: CGFloat = 28 let denyButton = OWSButton() - denyButton.autoSetDimensions(to: CGSize(square: buttonHeight)) denyButton.layer.cornerRadius = buttonHeight / 2 denyButton.clipsToBounds = true denyButton.setBackgroundImage(UIImage(color: Theme.secondaryBackgroundColor), for: .normal) @@ -196,7 +195,6 @@ public class GroupMemberRequestsAndInvitesViewController: OWSTableViewController } let approveButton = OWSButton() - approveButton.autoSetDimensions(to: CGSize(square: buttonHeight)) approveButton.layer.cornerRadius = buttonHeight / 2 approveButton.clipsToBounds = true approveButton.setBackgroundImage(UIImage(color: Theme.secondaryBackgroundColor), for: .normal) @@ -206,10 +204,25 @@ public class GroupMemberRequestsAndInvitesViewController: OWSTableViewController self?.approveMemberRequest(address: address) } - let stackView = UIStackView(arrangedSubviews: [denyButton, approveButton]) - stackView.axis = .horizontal - stackView.spacing = 16 - return stackView + let denyWrapper = ManualLayoutView.wrapSubviewUsingIOSAutoLayout(denyButton) + let approveWrapper = ManualLayoutView.wrapSubviewUsingIOSAutoLayout(approveButton) + + let denyButtonSize = CGSize.square(buttonHeight) + let approveButtonSize = CGSize.square(buttonHeight) + + let stackView = ManualStackView(name: "stackView") + let stackConfig = CVStackViewConfig(axis: .horizontal, + alignment: .center, + spacing: 16, + layoutMargins: .zero) + let stackMeasurement = stackView.configure(config: stackConfig, + subviews: [denyWrapper, approveWrapper], + subviewInfos: [ + denyButtonSize.asManualSubviewInfo, + approveButtonSize.asManualSubviewInfo + ]) + let stackSize = stackMeasurement.measuredSize + return ContactCellAccessoryView(accessoryView: stackView, size: stackSize) } private func approveMemberRequest(address: SignalServiceAddress) { diff --git a/Signal/src/ViewControllers/ThreadSettings/ReplaceAdminViewController.swift b/Signal/src/ViewControllers/ThreadSettings/ReplaceAdminViewController.swift index ee6048a90c..b8b7e9160a 100644 --- a/Signal/src/ViewControllers/ThreadSettings/ReplaceAdminViewController.swift +++ b/Signal/src/ViewControllers/ThreadSettings/ReplaceAdminViewController.swift @@ -62,9 +62,9 @@ class ReplaceAdminViewController: OWSTableViewController2 { localUserDisplayMode: .asUser, transaction: transaction) - let imageView = UIImageView() + let imageView = CVImageView() imageView.setTemplateImageName("empty-circle-outline-24", tintColor: .ows_gray25) - configuration.accessoryView = imageView + configuration.accessoryView = ContactCellAccessoryView(accessoryView: imageView, size: .square(24)) cell.configure(configuration: configuration, transaction: transaction) } diff --git a/SignalMessaging/ViewControllers/ConversationPicker/ConversationPicker.swift b/SignalMessaging/ViewControllers/ConversationPicker/ConversationPicker.swift index 66bc80df49..265a6cf54b 100644 --- a/SignalMessaging/ViewControllers/ConversationPicker/ConversationPicker.swift +++ b/SignalMessaging/ViewControllers/ConversationPicker/ConversationPicker.swift @@ -687,21 +687,33 @@ private class ConversationPickerCell: ContactTableViewCell { return container }() - func buildAccessoryView(disappearingMessagesConfig: OWSDisappearingMessagesConfiguration?) -> UIView { + func buildAccessoryView(disappearingMessagesConfig: OWSDisappearingMessagesConfiguration?) -> ContactCellAccessoryView { + + let selectionWrapper = ManualLayoutView.wrapSubviewUsingIOSAutoLayout(selectionView) + guard let disappearingMessagesConfig = disappearingMessagesConfig, disappearingMessagesConfig.isEnabled else { - return selectionView + return ContactCellAccessoryView(accessoryView: selectionWrapper, + size: selectionBadgeSize) } let timerView = DisappearingTimerConfigurationView(durationSeconds: disappearingMessagesConfig.durationSeconds) timerView.tintColor = Theme.middleGrayColor - timerView.autoSetDimensions(to: CGSize(square: 44)) - timerView.setCompressionResistanceHigh() + let timerSize = CGSize(square: 44) - let stackView = UIStackView(arrangedSubviews: [timerView, selectionView]) - stackView.alignment = .center - stackView.setCompressionResistanceHigh() - return stackView + let stackView = ManualStackView(name: "stackView") + let stackConfig = OWSStackView.Config(axis: .horizontal, + alignment: .center, + spacing: 0, + layoutMargins: .zero) + let stackMeasurement = stackView.configure(config: stackConfig, + subviews: [timerView, selectionWrapper], + subviewInfos: [ + timerSize.asManualSubviewInfo, + selectionBadgeSize.asManualSubviewInfo + ]) + let stackSize = stackMeasurement.measuredSize + return ContactCellAccessoryView(accessoryView: stackView, size: stackSize) } lazy var unselectedBadgeView: UIView = { diff --git a/SignalMessaging/Views/ContactCellView.swift b/SignalMessaging/Views/ContactCellView.swift index 7e92bc6a8b..8bea7851f3 100644 --- a/SignalMessaging/Views/ContactCellView.swift +++ b/SignalMessaging/Views/ContactCellView.swift @@ -4,6 +4,19 @@ import Foundation +@objc +public class ContactCellAccessoryView: NSObject { + let accessoryView: UIView + let size: CGSize + + public init(accessoryView: UIView, size: CGSize) { + self.accessoryView = accessoryView + self.size = size + } +} + +// MARK: - + @objc public class ContactCellConfiguration: NSObject { public let content: ConversationContent @@ -24,7 +37,7 @@ public class ContactCellConfiguration: NSObject { public var customName: String? @objc - public var accessoryView: UIView? + public var accessoryView: ContactCellAccessoryView? @objc public var attributedSubtitle: NSAttributedString? @@ -184,24 +197,22 @@ public class ContactCellView: ManualStackView { alignment: .leading, spacing: 0, layoutMargins: .zero) - let textStackMeasurement = ManualStackView.measure(config: textStackConfig, - subviewInfos: textStackSubviewInfos) - textStack.configure(config: textStackConfig, - measurement: textStackMeasurement, - subviews: textStackSubviews) + let textStackMeasurement = textStack.configure(config: textStackConfig, + subviews: textStackSubviews, + subviewInfos: textStackSubviewInfos) rootStackSubviews.append(textStack) rootStackSubviewInfos.append(textStackMeasurement.measuredSize.asManualSubviewInfo) } if let accessoryMessage = configuration.accessoryMessage { accessoryLabel.text = accessoryMessage - owsAssertDebug(configuration.accessoryView == nil) - configuration.accessoryView = accessoryLabel + let labelSize = accessoryLabel.sizeThatFits(.square(.greatestFiniteMagnitude)) + configuration.accessoryView = ContactCellAccessoryView(accessoryView: accessoryLabel, + size: labelSize) } if let accessoryView = configuration.accessoryView { - rootStackSubviews.append(accessoryView) - let accessorySize = accessoryView.sizeThatFits(.square(.greatestFiniteMagnitude)) - rootStackSubviewInfos.append(accessorySize.asManualSubviewInfo(hasFixedSize: true)) + rootStackSubviews.append(accessoryView.accessoryView) + rootStackSubviewInfos.append(accessoryView.size.asManualSubviewInfo(hasFixedSize: true)) } let rootStackConfig = ManualStackView.Config(axis: .horizontal, diff --git a/SignalMessaging/Views/ConversationView/CVText.swift b/SignalMessaging/Views/ConversationView/CVText.swift index 3d14b51bfd..a070344b8d 100644 --- a/SignalMessaging/Views/ConversationView/CVText.swift +++ b/SignalMessaging/Views/ConversationView/CVText.swift @@ -303,14 +303,17 @@ public class CVText { } let result: CGSize - switch mode { - case .layoutManager: - result = measureLabelUsingLayoutManager(config: config, maxWidth: maxWidth) - case .view: - result = measureLabelUsingView(config: config, maxWidth: maxWidth) + if config.text.stringValue.isEmpty { + result = .zero + } else { + switch mode { + case .layoutManager: + result = measureLabelUsingLayoutManager(config: config, maxWidth: maxWidth) + case .view: + result = measureLabelUsingView(config: config, maxWidth: maxWidth) + } + owsAssertDebug(result.isNonEmpty) } - owsAssertDebug(result.width > 0) - owsAssertDebug(result.height > 0) if cacheMeasurements { labelCache.set(key: cacheKey, value: result.ceil) diff --git a/SignalMessaging/Views/ManualStackView.swift b/SignalMessaging/Views/ManualStackView.swift index d552c6631c..605ba447bd 100644 --- a/SignalMessaging/Views/ManualStackView.swift +++ b/SignalMessaging/Views/ManualStackView.swift @@ -703,3 +703,18 @@ public struct ManualStackMeasurement: Equatable { ManualStackMeasurement(measuredSize: measuredSize, subviewInfos: []) } } + +// MARK: - + +public extension ManualStackView { + @discardableResult + func configure( + config: Config, + subviews: [UIView], + subviewInfos: [ManualStackSubviewInfo] + ) -> Measurement { + let measurement = ManualStackView.measure(config: config, subviewInfos: subviewInfos) + self.configure(config: config, measurement: measurement, subviews: subviews) + return measurement + } +}