Skip to content

Commit 0fbaa5a

Browse files
committed
Refactor buttons to use UIButton.Configuration
1 parent c28b222 commit 0fbaa5a

File tree

10 files changed

+147
-284
lines changed

10 files changed

+147
-284
lines changed

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

+8-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 */; };
@@ -896,6 +895,8 @@
896895
F06045E62B231EB700B2D37A /* URLSessionTransport.swift in Sources */ = {isa = PBXBuildFile; fileRef = F06045E52B231EB700B2D37A /* URLSessionTransport.swift */; };
897896
F06045EA2B23217E00B2D37A /* ShadowsocksTransport.swift in Sources */ = {isa = PBXBuildFile; fileRef = F06045E92B23217E00B2D37A /* ShadowsocksTransport.swift */; };
898897
F06045EC2B2322A500B2D37A /* Jittered.swift in Sources */ = {isa = PBXBuildFile; fileRef = F06045EB2B2322A500B2D37A /* Jittered.swift */; };
898+
F062000A2CB7EB42002E6DB9 /* CGSize+Helpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = F06200092CB7EB42002E6DB9 /* CGSize+Helpers.swift */; };
899+
F062000C2CB7EB5D002E6DB9 /* UIImage+Helpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = F062000B2CB7EB5D002E6DB9 /* UIImage+Helpers.swift */; };
899900
F062B94D2C16E09700B6D47A /* TunnelSettingsManagerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F062B94C2C16E09700B6D47A /* TunnelSettingsManagerTests.swift */; };
900901
F072D3CF2C07122400906F64 /* SettingsUpdaterTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F072D3CE2C07122400906F64 /* SettingsUpdaterTests.swift */; };
901902
F072D3D22C071AD100906F64 /* ShadowsocksLoaderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F072D3D12C071AD100906F64 /* ShadowsocksLoaderTests.swift */; };
@@ -1785,7 +1786,6 @@
17851786
58FF9FE32B075BDD00E4C97D /* EditAccessMethodItemIdentifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditAccessMethodItemIdentifier.swift; sourceTree = "<group>"; };
17861787
58FF9FE72B07650A00E4C97D /* ButtonCellContentConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ButtonCellContentConfiguration.swift; sourceTree = "<group>"; };
17871788
58FF9FE92B07653800E4C97D /* ButtonCellContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ButtonCellContentView.swift; sourceTree = "<group>"; };
1788-
58FF9FEB2B07A7CB00E4C97D /* NSDirectionalEdgeInsets+Helpers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSDirectionalEdgeInsets+Helpers.swift"; sourceTree = "<group>"; };
17891789
58FF9FEF2B07C4D300E4C97D /* PersistentAccessMethod+ViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "PersistentAccessMethod+ViewModel.swift"; sourceTree = "<group>"; };
17901790
58FF9FF32B07C61B00E4C97D /* AccessMethodValidationError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccessMethodValidationError.swift; sourceTree = "<group>"; };
17911791
7A02D4EA2A9CEC7A00C19E31 /* MullvadVPNScreenshots.xctestplan */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = MullvadVPNScreenshots.xctestplan; sourceTree = "<group>"; };
@@ -2117,6 +2117,8 @@
21172117
F06045E52B231EB700B2D37A /* URLSessionTransport.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = URLSessionTransport.swift; sourceTree = "<group>"; };
21182118
F06045E92B23217E00B2D37A /* ShadowsocksTransport.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ShadowsocksTransport.swift; sourceTree = "<group>"; };
21192119
F06045EB2B2322A500B2D37A /* Jittered.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Jittered.swift; sourceTree = "<group>"; };
2120+
F06200092CB7EB42002E6DB9 /* CGSize+Helpers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CGSize+Helpers.swift"; sourceTree = "<group>"; };
2121+
F062000B2CB7EB5D002E6DB9 /* UIImage+Helpers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIImage+Helpers.swift"; sourceTree = "<group>"; };
21202122
F062B94C2C16E09700B6D47A /* TunnelSettingsManagerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TunnelSettingsManagerTests.swift; sourceTree = "<group>"; };
21212123
F072D3CE2C07122400906F64 /* SettingsUpdaterTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsUpdaterTests.swift; sourceTree = "<group>"; };
21222124
F072D3D12C071AD100906F64 /* ShadowsocksLoaderTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShadowsocksLoaderTests.swift; sourceTree = "<group>"; };
@@ -2959,6 +2961,7 @@
29592961
isa = PBXGroup;
29602962
children = (
29612963
5891BF1B25E3E3EB006D6FB0 /* Bundle+ProductVersion.swift */,
2964+
F06200092CB7EB42002E6DB9 /* CGSize+Helpers.swift */,
29622965
587EB669270EFACB00123C75 /* CharacterSet+IPAddress.swift */,
29632966
58E511E528DDDEAC00B0BCDE /* CodingErrors+CustomErrorDescription.swift */,
29642967
7AF9BE8F2A39F26000DBFEDB /* Collection+Sorting.swift */,
@@ -2980,6 +2983,7 @@
29802983
5891BF5025E66B1E006D6FB0 /* UIBarButtonItem+KeyboardNavigation.swift */,
29812984
587CBFE222807F530028DED3 /* UIColor+Helpers.swift */,
29822985
7ABE318C2A1CDD4500DF4963 /* UIFont+Weight.swift */,
2986+
F062000B2CB7EB5D002E6DB9 /* UIImage+Helpers.swift */,
29832987
58CEB2FA2AFD13E600E6E088 /* UIListContentConfiguration+Extensions.swift */,
29842988
58CEB2FC2AFD19D300E6E088 /* UITableView+ReuseIdentifier.swift */,
29852989
7A58699A2B482FE200640D27 /* UITableViewCell+Disable.swift */,
@@ -3015,7 +3019,6 @@
30153019
children = (
30163020
58CCA0152242560B004F3011 /* UIColor+Palette.swift */,
30173021
A9E034632ABB302000E59A5A /* UIEdgeInsets+Extensions.swift */,
3018-
58FF9FEB2B07A7CB00E4C97D /* NSDirectionalEdgeInsets+Helpers.swift */,
30193022
585CA70E25F8C44600B47C62 /* UIMetrics.swift */,
30203023
);
30213024
path = "UI appearance";
@@ -5630,6 +5633,7 @@
56305633
587EB672271451E300123C75 /* VPNSettingsViewModel.swift in Sources */,
56315634
586A950C290125EE007BAF2B /* AlertPresenter.swift in Sources */,
56325635
7A9FA1422A2E3306000B728D /* CheckboxView.swift in Sources */,
5636+
F062000A2CB7EB42002E6DB9 /* CGSize+Helpers.swift in Sources */,
56335637
586C0D892B03D5E000E7CDD7 /* TextCellContentConfiguration+Extensions.swift in Sources */,
56345638
58C3F4F92964B08300D72515 /* MapViewController.swift in Sources */,
56355639
584D26C6270C8741004EA533 /* SettingsDNSTextCell.swift in Sources */,
@@ -5643,6 +5647,7 @@
56435647
7A516C2E2B6D357500BBD33D /* URL+Scoping.swift in Sources */,
56445648
5878A27529093A310096FC88 /* StorePaymentEvent.swift in Sources */,
56455649
7A7AD28D29DC677800480EF1 /* FirstTimeLaunch.swift in Sources */,
5650+
F062000C2CB7EB5D002E6DB9 /* UIImage+Helpers.swift in Sources */,
56465651
7A6389EB2B7FAD7A008E77E1 /* SettingsFieldValidationErrorContentView.swift in Sources */,
56475652
58B26E2A2943545A00D5980C /* NotificationManagerDelegate.swift in Sources */,
56485653
58A1AA8C23F5584C009F7EA6 /* ConnectionPanelView.swift in Sources */,
@@ -5839,7 +5844,6 @@
58395844
7A5869B92B56E7F000640D27 /* IPOverrideViewControllerDelegate.swift in Sources */,
58405845
586C0D7A2B039CE300E7CDD7 /* ShadowsocksCipherPicker.swift in Sources */,
58415846
58B93A1326C3F13600A55733 /* TunnelState.swift in Sources */,
5842-
58FF9FEC2B07A7CB00E4C97D /* NSDirectionalEdgeInsets+Helpers.swift in Sources */,
58435847
586C0D832B03D2FF00E7CDD7 /* ShadowsocksSectionHandler.swift in Sources */,
58445848
58B26E262943522400D5980C /* NotificationProvider.swift in Sources */,
58455849
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/Extensions/CGSize+Helpers.swift

+18
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
//
2+
// CGSize+Helpers.swift
3+
// MullvadVPN
4+
//
5+
// Created by Mojgan on 2024-10-10.
6+
// Copyright © 2024 Mullvad VPN AB. All rights reserved.
7+
//
8+
9+
import UIKit
10+
11+
extension CGSize {
12+
// Function to deduct insets from CGSize
13+
func deducting(insets: NSDirectionalEdgeInsets) -> CGSize {
14+
let newWidth = width - (insets.leading + insets.trailing)
15+
let newHeight = height - (insets.top + insets.bottom)
16+
return CGSize(width: newWidth, height: newHeight)
17+
}
18+
}

Diff for: ios/MullvadVPN/Extensions/UIImage+Helpers.swift

+29
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
//
2+
// UIImage+Helpers.swift
3+
// MullvadVPN
4+
//
5+
// Created by Mojgan on 2024-10-10.
6+
// Copyright © 2024 Mullvad VPN AB. All rights reserved.
7+
//
8+
9+
import UIKit
10+
11+
extension UIImage {
12+
// Function to resize image while keeping aspect ratio
13+
func resizeImage(targetSize: CGSize) -> UIImage {
14+
let widthRatio = targetSize.width / size.width
15+
let heightRatio = targetSize.height / size.height
16+
let scaleFactor = min(widthRatio, heightRatio)
17+
18+
// Calculate new size based on the scale factor
19+
let newSize = CGSize(width: size.width * scaleFactor, height: size.height * scaleFactor)
20+
let renderer = UIGraphicsImageRenderer(size: newSize)
21+
22+
// Render the new image
23+
let resizedImage = renderer.image { _ in
24+
draw(in: CGRect(origin: .zero, size: newSize))
25+
}
26+
27+
return resizedImage.withRenderingMode(renderingMode)
28+
}
29+
}

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

-21
This file was deleted.

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

+16-10
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

112-
private let removeLastUsedAccountButton: UIButton = {
113-
let button = UIButton(type: .custom)
118+
private let removeLastUsedAccountButton: CustomButton = {
119+
let button = CustomButton()
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

+21-57
Original file line numberDiff line numberDiff line change
@@ -10,80 +10,44 @@ 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

20-
private let secondaryButtonWidthConstraint: NSLayoutConstraint
21-
private let secondaryButtonHeightConstraint: NSLayoutConstraint
16+
override init(frame: CGRect) {
17+
super.init(frame: .zero)
18+
commonInit()
19+
}
2220

23-
private let stackView: UIStackView
21+
required init?(coder: NSCoder) {
22+
fatalError("init(coder:) has not been implemented")
23+
}
2424

25-
init() {
25+
private func commonInit() {
2626
let primaryButtonBlurView = TranslucentButtonBlurView(button: primaryButton)
2727
let secondaryButtonBlurView = TranslucentButtonBlurView(button: secondaryButton)
2828

29-
stackView = UIStackView(arrangedSubviews: [primaryButtonBlurView, secondaryButtonBlurView])
29+
let stackView = UIStackView(arrangedSubviews: [primaryButtonBlurView, secondaryButtonBlurView])
3030
stackView.translatesAutoresizingMaskIntoConstraints = false
3131
stackView.axis = .horizontal
3232
stackView.distribution = .fill
3333
stackView.alignment = .fill
3434
stackView.spacing = 1
3535

36-
secondaryButton.setImage(
37-
UIImage(named: "IconReload")?.imageFlippedForRightToLeftLayoutDirection(),
38-
for: .normal
39-
)
40-
41-
primaryButton.overrideContentEdgeInsets = true
42-
secondaryButtonWidthConstraint = secondaryButton.widthAnchor.constraint(equalToConstant: 0)
43-
secondaryButtonHeightConstraint = secondaryButton.heightAnchor
44-
.constraint(equalToConstant: 0)
45-
46-
super.init(frame: .zero)
47-
48-
addSubview(stackView)
49-
50-
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-
56-
secondaryButtonWidthConstraint,
57-
secondaryButtonHeightConstraint,
58-
])
59-
60-
updateTraitConstraints()
61-
}
62-
63-
required init?(coder: NSCoder) {
64-
fatalError("init(coder:) has not been implemented")
65-
}
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
36+
let secondaryButtonSize = UIMetrics.DisconnectSplitButton.secondaryButton
7637

77-
let offset = stackView.spacing + secondaryButtonSize.width
38+
addConstrainedSubviews([stackView]) {
39+
stackView.pinEdgesToSuperview()
7840

79-
if case .leftToRight = effectiveUserInterfaceLayoutDirection {
80-
contentInsets.left = offset
81-
contentInsets.right = 0
82-
} else {
83-
contentInsets.left = 0
84-
contentInsets.right = offset
41+
secondaryButton.widthAnchor.constraint(equalToConstant: secondaryButtonSize.width)
42+
secondaryButton.heightAnchor.constraint(equalToConstant: secondaryButtonSize.height)
8543
}
8644

87-
primaryButton.contentEdgeInsets = contentInsets
45+
// Ideally, we shouldn't need to manually resize the image ourselves.
46+
// However, since UIButton.Configuration doesn't provide a direct property
47+
// for controlling image scaling (like imageScaling or contentMode in other contexts),
48+
// manual resizing has been one approach to ensure the image fits within bounds.
49+
secondaryButton.configuration?.image = UIImage(resource: .iconReload)
50+
.resizeImage(targetSize: secondaryButtonSize.deducting(insets: secondaryButton.defaultContentInsets))
51+
.imageFlippedForRightToLeftLayoutDirection()
8852
}
8953
}

0 commit comments

Comments
 (0)