1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
|
//
// LocationSectionHeaderView.swift
// MullvadVPN
//
// Created by Mojgan on 2024-01-25.
// Copyright © 2025 Mullvad VPN AB. All rights reserved.
//
import Foundation
import UIKit
class LocationSectionHeaderFooterView: UIView, UIContentView {
var configuration: UIContentConfiguration {
get {
actualConfiguration
} set {
guard let newConfiguration = newValue as? Configuration,
actualConfiguration != newConfiguration else { return }
actualConfiguration = newConfiguration
apply(configuration: newConfiguration)
}
}
private var actualConfiguration: Configuration
private let containerView: UIStackView = {
let containerView = UIStackView()
containerView.axis = .horizontal
containerView.spacing = 8
containerView.isLayoutMarginsRelativeArrangement = true
return containerView
}()
private let nameLabel: UILabel = {
let label = UILabel()
label.numberOfLines = 0
label.lineBreakMode = .byWordWrapping
label.textColor = .primaryTextColor
label.font = .systemFont(ofSize: 16, weight: .semibold)
return label
}()
private let actionButton: UIButton = {
let button = UIButton(type: .system)
button.setImage(UIImage(systemName: "ellipsis"), for: .normal)
button.tintColor = UIColor(white: 1, alpha: 0.6)
return button
}()
init(configuration: Configuration) {
self.actualConfiguration = configuration
super.init(frame: .zero)
apply(configuration: configuration)
addSubviews()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
private func addSubviews() {
containerView.addArrangedSubview(nameLabel)
containerView.addArrangedSubview(actionButton)
addConstrainedSubviews([containerView]) {
containerView.pinEdgesToSuperviewMargins()
actionButton.widthAnchor.constraint(equalTo: actionButton.heightAnchor)
}
}
private func apply(configuration: Configuration) {
let isActionHidden = configuration.primaryAction == nil
backgroundColor = configuration.style.backgroundColor
nameLabel.textColor = configuration.style.textColor
nameLabel.text = configuration.name
nameLabel.font = configuration.style.font
nameLabel.textAlignment = configuration.style.textAlignment
actionButton.isHidden = isActionHidden
actionButton.accessibilityIdentifier = nil
actualConfiguration.primaryAction.flatMap { action in
actionButton.setAccessibilityIdentifier(.openCustomListsMenuButton)
actionButton.addAction(action, for: .touchUpInside)
}
directionalLayoutMargins = actualConfiguration.directionalEdgeInsets
}
}
extension LocationSectionHeaderFooterView {
struct Style: Equatable {
let font: UIFont
let textColor: UIColor
let textAlignment: NSTextAlignment
let backgroundColor: UIColor
static let header = Style(
font: .preferredFont(forTextStyle: .body, weight: .semibold),
textColor: .primaryTextColor,
textAlignment: .natural,
backgroundColor: .primaryColor
)
static let footer = Style(
font: .preferredFont(forTextStyle: .body, weight: .regular),
textColor: .secondaryTextColor,
textAlignment: .center,
backgroundColor: .clear
)
}
struct Configuration: UIContentConfiguration, Equatable {
let name: String
let style: Style
var directionalEdgeInsets = NSDirectionalEdgeInsets(top: 0, leading: 16, bottom: 0, trailing: 16)
var primaryAction: UIAction?
func makeContentView() -> UIView & UIContentView {
LocationSectionHeaderFooterView(configuration: self)
}
func updated(for state: UIConfigurationState) -> Configuration {
self
}
}
}
|