Skip to content

Commit 47c0fcf

Browse files
committed
Refactor buttons to use UIButton.Configuration
1 parent adbd44a commit 47c0fcf

File tree

8 files changed

+85
-269
lines changed

8 files changed

+85
-269
lines changed

Diff for: ios/MullvadVPN.xcodeproj/project.pbxproj

-4
Original file line numberDiff line numberDiff line change
@@ -449,7 +449,6 @@
449449
58FF9FE42B075BDD00E4C97D /* EditAccessMethodItemIdentifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58FF9FE32B075BDD00E4C97D /* EditAccessMethodItemIdentifier.swift */; };
450450
58FF9FE82B07650A00E4C97D /* ButtonCellContentConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58FF9FE72B07650A00E4C97D /* ButtonCellContentConfiguration.swift */; };
451451
58FF9FEA2B07653800E4C97D /* ButtonCellContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58FF9FE92B07653800E4C97D /* ButtonCellContentView.swift */; };
452-
58FF9FEC2B07A7CB00E4C97D /* NSDirectionalEdgeInsets+Helpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58FF9FEB2B07A7CB00E4C97D /* NSDirectionalEdgeInsets+Helpers.swift */; };
453452
58FF9FF02B07C4D300E4C97D /* PersistentAccessMethod+ViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58FF9FEF2B07C4D300E4C97D /* PersistentAccessMethod+ViewModel.swift */; };
454453
58FF9FF42B07C61B00E4C97D /* AccessMethodValidationError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58FF9FF32B07C61B00E4C97D /* AccessMethodValidationError.swift */; };
455454
7A02D4EB2A9CEC7A00C19E31 /* MullvadVPNScreenshots.xctestplan in Resources */ = {isa = PBXBuildFile; fileRef = 7A02D4EA2A9CEC7A00C19E31 /* MullvadVPNScreenshots.xctestplan */; };
@@ -1782,7 +1781,6 @@
17821781
58FF9FE32B075BDD00E4C97D /* EditAccessMethodItemIdentifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditAccessMethodItemIdentifier.swift; sourceTree = "<group>"; };
17831782
58FF9FE72B07650A00E4C97D /* ButtonCellContentConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ButtonCellContentConfiguration.swift; sourceTree = "<group>"; };
17841783
58FF9FE92B07653800E4C97D /* ButtonCellContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ButtonCellContentView.swift; sourceTree = "<group>"; };
1785-
58FF9FEB2B07A7CB00E4C97D /* NSDirectionalEdgeInsets+Helpers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSDirectionalEdgeInsets+Helpers.swift"; sourceTree = "<group>"; };
17861784
58FF9FEF2B07C4D300E4C97D /* PersistentAccessMethod+ViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "PersistentAccessMethod+ViewModel.swift"; sourceTree = "<group>"; };
17871785
58FF9FF32B07C61B00E4C97D /* AccessMethodValidationError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccessMethodValidationError.swift; sourceTree = "<group>"; };
17881786
7A02D4EA2A9CEC7A00C19E31 /* MullvadVPNScreenshots.xctestplan */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = MullvadVPNScreenshots.xctestplan; sourceTree = "<group>"; };
@@ -3013,7 +3011,6 @@
30133011
children = (
30143012
58CCA0152242560B004F3011 /* UIColor+Palette.swift */,
30153013
A9E034632ABB302000E59A5A /* UIEdgeInsets+Extensions.swift */,
3016-
58FF9FEB2B07A7CB00E4C97D /* NSDirectionalEdgeInsets+Helpers.swift */,
30173014
585CA70E25F8C44600B47C62 /* UIMetrics.swift */,
30183015
);
30193016
path = "UI appearance";
@@ -5836,7 +5833,6 @@
58365833
7A5869B92B56E7F000640D27 /* IPOverrideViewControllerDelegate.swift in Sources */,
58375834
586C0D7A2B039CE300E7CDD7 /* ShadowsocksCipherPicker.swift in Sources */,
58385835
58B93A1326C3F13600A55733 /* TunnelState.swift in Sources */,
5839-
58FF9FEC2B07A7CB00E4C97D /* NSDirectionalEdgeInsets+Helpers.swift in Sources */,
58405836
586C0D832B03D2FF00E7CDD7 /* ShadowsocksSectionHandler.swift in Sources */,
58415837
58B26E262943522400D5980C /* NotificationProvider.swift in Sources */,
58425838
58CE5E64224146200008646E /* AppDelegate.swift in Sources */,

Diff for: ios/MullvadVPN/Coordinators/Settings/APIAccess/Cells/ButtonCellContentView.swift

+1-2
Original file line numberDiff line numberDiff line change
@@ -66,8 +66,7 @@ class ButtonCellContentView: UIView, UIContentView {
6666
button.titleLabel?.font = .systemFont(ofSize: 17)
6767
button.isEnabled = actualConfiguration.isEnabled
6868
button.style = actualConfiguration.style
69-
button.overrideContentEdgeInsets = true
70-
button.directionalContentEdgeInsets = actualConfiguration.directionalContentEdgeInsets
69+
button.configuration?.contentInsets = actualConfiguration.directionalContentEdgeInsets
7170
}
7271

7372
private func addSubviews() {

Diff for: ios/MullvadVPN/UI appearance/NSDirectionalEdgeInsets+Helpers.swift

-21
This file was deleted.

Diff for: ios/MullvadVPN/View controllers/Login/AccountInputGroupView.swift

+15-9
Original file line numberDiff line numberDiff line change
@@ -92,35 +92,41 @@ final class AccountInputGroupView: UIView {
9292
}()
9393

9494
private let lastUsedAccountButton: UIButton = {
95-
let button = UIButton(type: .system)
95+
let button = UIButton(configuration: .plain())
9696
button.translatesAutoresizingMaskIntoConstraints = false
97-
button.titleLabel?.font = accountNumberFont()
98-
button.setTitle(" ", for: .normal)
9997
button.contentHorizontalAlignment = .leading
100-
button.contentEdgeInsets = UIMetrics.textFieldMargins
101-
button.setTitleColor(UIColor.AccountTextField.NormalState.textColor, for: .normal)
10298
button.setContentCompressionResistancePriority(.defaultLow, for: .horizontal)
10399
button.accessibilityLabel = NSLocalizedString(
104100
"LAST_USED_ACCOUNT_ACCESSIBILITY_LABEL",
105101
tableName: "AccountInput",
106102
value: "Last used account",
107103
comment: ""
108104
)
105+
button.configuration?.contentInsets = UIMetrics.textFieldMargins.toDirectionalInsets
106+
button.configuration?.title = " "
107+
button.configuration?
108+
.titleTextAttributesTransformer = UIConfigurationTextAttributesTransformer { attributeContainer in
109+
var updatedAttributeContainer = attributeContainer
110+
updatedAttributeContainer.font = AccountInputGroupView.accountNumberFont()
111+
updatedAttributeContainer.foregroundColor = .AccountTextField.NormalState.textColor
112+
return updatedAttributeContainer
113+
}
114+
109115
return button
110116
}()
111117

112118
private let removeLastUsedAccountButton: UIButton = {
113-
let button = UIButton(type: .custom)
119+
let button = UIButton(configuration: .plain())
114120
button.translatesAutoresizingMaskIntoConstraints = false
115-
button.setImage(UIImage(named: "IconCloseSml"), for: .normal)
116-
button.imageView?.tintColor = .primaryColor.withAlphaComponent(0.4)
117121
button.setContentCompressionResistancePriority(.defaultHigh, for: .horizontal)
118122
button.accessibilityLabel = NSLocalizedString(
119123
"REMOVE_LAST_USED_ACCOUNT_ACCESSIBILITY_LABEL",
120124
tableName: "AccountInput",
121125
value: "Remove last used account",
122126
comment: ""
123127
)
128+
button.configuration?.image = UIImage(resource: .iconCloseSml).withTintColor(.primaryColor)
129+
button.configuration?.title = " "
124130
return button
125131
}()
126132

@@ -303,7 +309,7 @@ final class AccountInputGroupView: UIView {
303309
)
304310

305311
UIView.performWithoutAnimation {
306-
self.lastUsedAccountButton.setTitle(formattedNumber, for: .normal)
312+
self.lastUsedAccountButton.configuration?.title = formattedNumber
307313
self.lastUsedAccountButton.layoutIfNeeded()
308314
}
309315
}

Diff for: ios/MullvadVPN/View controllers/Tunnel/DisconnectSplitButton.swift

+14-42
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,6 @@ import Foundation
1010
import UIKit
1111

1212
class DisconnectSplitButton: UIView {
13-
private var secondaryButtonSize: CGSize {
14-
UIMetrics.DisconnectSplitButton.secondaryButton
15-
}
16-
1713
let primaryButton = AppButton(style: .translucentDangerSplitLeft)
1814
let secondaryButton = AppButton(style: .translucentDangerSplitRight)
1915

@@ -33,57 +29,33 @@ class DisconnectSplitButton: UIView {
3329
stackView.alignment = .fill
3430
stackView.spacing = 1
3531

36-
secondaryButton.setImage(
37-
UIImage(named: "IconReload")?.imageFlippedForRightToLeftLayoutDirection(),
38-
for: .normal
39-
)
32+
primaryButton.translatesAutoresizingMaskIntoConstraints = false
33+
secondaryButton.translatesAutoresizingMaskIntoConstraints = false
4034

41-
primaryButton.overrideContentEdgeInsets = true
42-
secondaryButtonWidthConstraint = secondaryButton.widthAnchor.constraint(equalToConstant: 0)
35+
let secondaryButtonSize = UIMetrics.DisconnectSplitButton.secondaryButton
36+
let image =
37+
UIImage(resource: .iconReload) // UIImage(resource: .iconReload).resizableImage(withCapInsets: secondaryButton.configuration?.contentInsets).imageFlippedForRightToLeftLayoutDirection()
38+
39+
secondaryButtonWidthConstraint = secondaryButton.widthAnchor
40+
.constraint(equalToConstant: secondaryButtonSize.width)
4341
secondaryButtonHeightConstraint = secondaryButton.heightAnchor
44-
.constraint(equalToConstant: 0)
42+
.constraint(equalToConstant: secondaryButtonSize.height)
4543

4644
super.init(frame: .zero)
4745

48-
addSubview(stackView)
46+
addConstrainedSubviews([stackView]) {
47+
stackView.pinEdgesToSuperview()
48+
}
4949

5050
NSLayoutConstraint.activate([
51-
stackView.leadingAnchor.constraint(equalTo: leadingAnchor),
52-
stackView.trailingAnchor.constraint(equalTo: trailingAnchor),
53-
stackView.topAnchor.constraint(equalTo: topAnchor),
54-
stackView.bottomAnchor.constraint(equalTo: bottomAnchor),
55-
5651
secondaryButtonWidthConstraint,
5752
secondaryButtonHeightConstraint,
5853
])
59-
60-
updateTraitConstraints()
54+
secondaryButton.imageView?.contentMode = .scaleAspectFit
55+
secondaryButton.imageView?.setNeedsDisplay()
6156
}
6257

6358
required init?(coder: NSCoder) {
6459
fatalError("init(coder:) has not been implemented")
6560
}
66-
67-
private func updateTraitConstraints() {
68-
let newSize = secondaryButtonSize
69-
secondaryButtonWidthConstraint.constant = newSize.width
70-
secondaryButtonHeightConstraint.constant = newSize.height
71-
adjustTitleLabelPosition()
72-
}
73-
74-
private func adjustTitleLabelPosition() {
75-
var contentInsets = primaryButton.defaultContentInsets
76-
77-
let offset = stackView.spacing + secondaryButtonSize.width
78-
79-
if case .leftToRight = effectiveUserInterfaceLayoutDirection {
80-
contentInsets.left = offset
81-
contentInsets.right = 0
82-
} else {
83-
contentInsets.left = 0
84-
contentInsets.right = offset
85-
}
86-
87-
primaryButton.contentEdgeInsets = contentInsets
88-
}
8961
}

Diff for: ios/MullvadVPN/Views/AppButton.swift

+22-47
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,12 @@ import UIKit
1111
/// A subclass that implements action buttons used across the app
1212
class AppButton: CustomButton {
1313
/// Default content insets based on current trait collection.
14-
var defaultContentInsets: UIEdgeInsets {
14+
var defaultContentInsets: NSDirectionalEdgeInsets {
1515
switch traitCollection.userInterfaceIdiom {
1616
case .phone:
17-
return UIEdgeInsets(top: 10, left: 10, bottom: 10, right: 10)
17+
return NSDirectionalEdgeInsets(top: 10, leading: 10, bottom: 10, trailing: 10)
1818
case .pad:
19-
return UIEdgeInsets(top: 15, left: 15, bottom: 15, right: 15)
19+
return NSDirectionalEdgeInsets(top: 15, leading: 15, bottom: 15, trailing: 15)
2020
default:
2121
return .zero
2222
}
@@ -87,30 +87,6 @@ class AppButton: CustomButton {
8787
}
8888
}
8989

90-
/// Prevents updating `contentEdgeInsets` on changes to trait collection.
91-
var overrideContentEdgeInsets = false
92-
93-
override var contentEdgeInsets: UIEdgeInsets {
94-
didSet {
95-
// Reset inner directional insets when contentEdgeInsets are set directly.
96-
innerDirectionalContentEdgeInsets = nil
97-
}
98-
}
99-
100-
/// Directional content edge insets that are automatically translated into `contentEdgeInsets` immeditely upon updating the property and on trait collection
101-
/// changes.
102-
var directionalContentEdgeInsets: NSDirectionalEdgeInsets {
103-
get {
104-
innerDirectionalContentEdgeInsets ?? contentEdgeInsets.toDirectionalInsets
105-
}
106-
set {
107-
innerDirectionalContentEdgeInsets = newValue
108-
updateContentEdgeInsetsFromDirectional()
109-
}
110-
}
111-
112-
private var innerDirectionalContentEdgeInsets: NSDirectionalEdgeInsets?
113-
11490
init(style: Style) {
11591
self.style = style
11692
super.init(frame: .zero)
@@ -128,32 +104,31 @@ class AppButton: CustomButton {
128104
}
129105

130106
private func commonInit() {
131-
super.contentEdgeInsets = defaultContentInsets
132-
imageAlignment = .trailingFixed
133-
134-
titleLabel?.font = UIFont.systemFont(ofSize: 18, weight: .semibold)
135-
136-
let states: [UIControl.State] = [.normal, .highlighted, .disabled]
137-
states.forEach { state in
138-
if let titleColor = state.customButtonTitleColor {
139-
setTitleColor(titleColor, for: state)
107+
imageAlignment = .trailing
108+
titleAlignment = .leading
109+
110+
var config = super.configuration ?? .plain()
111+
config.title = title(for: state)
112+
config.contentInsets = defaultContentInsets
113+
config.background.image = style.backgroundImage
114+
config.titleTextAttributesTransformer =
115+
UIConfigurationTextAttributesTransformer { [weak self] attributeContainer in
116+
guard let self = self else { return attributeContainer }
117+
var updatedAttributeContainer = attributeContainer
118+
updatedAttributeContainer.font = UIFont.systemFont(ofSize: 18, weight: .semibold)
119+
updatedAttributeContainer.foregroundColor = self.state.customButtonTitleColor
120+
return updatedAttributeContainer
140121
}
141-
}
142122

143-
// Avoid setting the background image if it's already set via Interface Builder
144-
if backgroundImage(for: .normal) == nil {
145-
updateButtonBackground()
123+
let configurationHandler: UIButton.ConfigurationUpdateHandler = { button in
124+
button.alpha = button.state == .disabled ? 0.5 : 1.0
146125
}
126+
self.configuration = config
127+
self.configurationUpdateHandler = configurationHandler
147128
}
148129

149130
/// Set background image based on current style.
150131
private func updateButtonBackground() {
151-
setBackgroundImage(style.backgroundImage, for: .normal)
152-
}
153-
154-
/// Update content edge insets from directional edge insets if set.
155-
private func updateContentEdgeInsetsFromDirectional() {
156-
guard let directionalEdgeInsets = innerDirectionalContentEdgeInsets else { return }
157-
super.contentEdgeInsets = directionalEdgeInsets.toEdgeInsets(effectiveUserInterfaceLayoutDirection)
132+
self.configuration?.background.image = style.backgroundImage
158133
}
159134
}

0 commit comments

Comments
 (0)