Skip to content

Commit 2842132

Browse files
Merge pull request #64 from componentskit/UKAvatarGroup
UKAvatarGroup
2 parents a14ab1d + e80ac22 commit 2842132

File tree

5 files changed

+235
-5
lines changed

5 files changed

+235
-5
lines changed

Examples/DemosApp/DemosApp/ComponentsPreview/PreviewPages/AvatarGroupPreview.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,10 @@ struct AvatarGroupPreview: View {
2727

2828
var body: some View {
2929
VStack {
30+
PreviewWrapper(title: "UIKit") {
31+
UKAvatarGroup(model: self.model)
32+
.preview
33+
}
3034
PreviewWrapper(title: "SwiftUI") {
3135
SUAvatarGroup(model: self.model)
3236
}

Sources/ComponentsKit/Components/AvatarGroup/Models/AvatarGroupVM.swift

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ extension AvatarGroupVM {
7070
return avatars
7171
}
7272

73-
var avatarSize: CGSize {
73+
var itemSize: CGSize {
7474
switch self.size {
7575
case .small:
7676
return .init(width: 36, height: 36)
@@ -92,7 +92,22 @@ extension AvatarGroupVM {
9292
}
9393
}
9494

95+
var spacing: CGFloat {
96+
return -self.itemSize.width / 3
97+
}
98+
9599
var numberOfHiddenAvatars: Int {
96-
return self.items.count - self.maxVisibleAvatars
100+
return max(0, self.items.count - self.maxVisibleAvatars)
101+
}
102+
}
103+
104+
// MARK: - UIKit Helpers
105+
106+
extension AvatarGroupVM {
107+
var avatarHeight: CGFloat {
108+
return self.itemSize.height - self.padding * 2
109+
}
110+
var avatarWidth: CGFloat {
111+
return self.itemSize.width - self.padding * 2
97112
}
98113
}

Sources/ComponentsKit/Components/AvatarGroup/SUAvatarGroup.swift renamed to Sources/ComponentsKit/Components/AvatarGroup/SwiftUI/SUAvatarGroup.swift

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ public struct SUAvatarGroup: View {
1919
// MARK: - Body
2020

2121
public var body: some View {
22-
HStack(spacing: -self.model.avatarSize.width / 3) {
22+
HStack(spacing: self.model.spacing) {
2323
ForEach(self.model.identifiedAvatarVMs, id: \.0) { _, avatarVM in
2424
AvatarContent(model: avatarVM)
2525
.padding(self.model.padding)
@@ -28,8 +28,8 @@ public struct SUAvatarGroup: View {
2828
RoundedRectangle(cornerRadius: self.model.cornerRadius.value())
2929
)
3030
.frame(
31-
width: self.model.avatarSize.width,
32-
height: self.model.avatarSize.height
31+
width: self.model.itemSize.width,
32+
height: self.model.itemSize.height
3333
)
3434
}
3535
}
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
import AutoLayout
2+
import UIKit
3+
4+
final class AvatarContainer: UIView {
5+
// MARK: - Properties
6+
7+
let avatar: UKAvatar
8+
var groupVM: AvatarGroupVM
9+
var avatarConstraints = LayoutConstraints()
10+
11+
// MARK: - Initialization
12+
13+
init(avatarVM: AvatarVM, groupVM: AvatarGroupVM) {
14+
self.avatar = UKAvatar(model: avatarVM)
15+
self.groupVM = groupVM
16+
17+
super.init(frame: .zero)
18+
19+
self.setup()
20+
self.style()
21+
self.layout()
22+
}
23+
24+
required init?(coder: NSCoder) {
25+
fatalError("init(coder:) has not been implemented")
26+
}
27+
28+
// MARK: - Setup
29+
30+
func setup() {
31+
self.addSubview(self.avatar)
32+
}
33+
34+
// MARK: - Style
35+
36+
func style() {
37+
Self.Style.mainView(self, model: self.groupVM)
38+
}
39+
40+
// MARK: - Layout
41+
42+
func layout() {
43+
self.avatarConstraints = .merged {
44+
self.avatar.allEdges(self.groupVM.padding)
45+
self.avatar.height(self.groupVM.avatarHeight)
46+
self.avatar.width(self.groupVM.avatarWidth)
47+
}
48+
49+
self.avatarConstraints.height?.priority = .defaultHigh
50+
self.avatarConstraints.width?.priority = .defaultHigh
51+
}
52+
53+
override func layoutSubviews() {
54+
super.layoutSubviews()
55+
56+
self.layer.cornerRadius = self.groupVM.cornerRadius.value(for: self.bounds.height)
57+
}
58+
59+
// MARK: - Update
60+
61+
func update(avatarVM: AvatarVM, groupVM: AvatarGroupVM) {
62+
let oldModel = self.groupVM
63+
self.groupVM = groupVM
64+
65+
if self.groupVM.size != oldModel.size {
66+
self.avatarConstraints.top?.constant = groupVM.padding
67+
self.avatarConstraints.leading?.constant = groupVM.padding
68+
self.avatarConstraints.bottom?.constant = -groupVM.padding
69+
self.avatarConstraints.trailing?.constant = -groupVM.padding
70+
self.avatarConstraints.height?.constant = groupVM.avatarHeight
71+
self.avatarConstraints.width?.constant = groupVM.avatarWidth
72+
73+
self.setNeedsLayout()
74+
}
75+
76+
self.avatar.model = avatarVM
77+
self.style()
78+
}
79+
}
80+
81+
// MARK: - Style Helpers
82+
83+
extension AvatarContainer {
84+
fileprivate enum Style {
85+
static func mainView(_ view: UIView, model: AvatarGroupVM) {
86+
view.backgroundColor = model.borderColor.uiColor
87+
view.layer.cornerRadius = model.cornerRadius.value(for: view.bounds.height)
88+
}
89+
}
90+
}
Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
import AutoLayout
2+
import UIKit
3+
4+
/// A UIKit component that displays a group of avatars.
5+
open class UKAvatarGroup: UIView, UKComponent {
6+
// MARK: - Properties
7+
8+
/// A model that defines the appearance properties.
9+
public var model: AvatarGroupVM {
10+
didSet {
11+
self.update(oldValue)
12+
}
13+
}
14+
15+
// MARK: - Subviews
16+
17+
/// The stack view that contains avatars.
18+
public var stackView = UIStackView()
19+
20+
// MARK: - Initializers
21+
22+
/// Initializer.
23+
/// - Parameters:
24+
/// - model: A model that defines the appearance properties.
25+
public init(model: AvatarGroupVM = .init()) {
26+
self.model = model
27+
28+
super.init(frame: .zero)
29+
30+
self.setup()
31+
self.style()
32+
self.layout()
33+
}
34+
35+
public required init?(coder: NSCoder) {
36+
fatalError("init(coder:) has not been implemented")
37+
}
38+
39+
// MARK: - Setup
40+
41+
private func setup() {
42+
self.addSubview(self.stackView)
43+
self.model.identifiedAvatarVMs.forEach { _, avatarVM in
44+
self.stackView.addArrangedSubview(AvatarContainer(
45+
avatarVM: avatarVM,
46+
groupVM: self.model
47+
))
48+
}
49+
}
50+
51+
// MARK: - Style
52+
53+
private func style() {
54+
Self.Style.stackView(self.stackView, model: self.model)
55+
}
56+
57+
// MARK: - Layout
58+
59+
private func layout() {
60+
self.stackView.centerVertically()
61+
self.stackView.centerHorizontally()
62+
63+
self.stackView.topAnchor.constraint(
64+
greaterThanOrEqualTo: self.topAnchor
65+
).isActive = true
66+
self.stackView.bottomAnchor.constraint(
67+
lessThanOrEqualTo: self.bottomAnchor
68+
).isActive = true
69+
self.stackView.leadingAnchor.constraint(
70+
greaterThanOrEqualTo: self.leadingAnchor
71+
).isActive = true
72+
self.stackView.trailingAnchor.constraint(
73+
lessThanOrEqualTo: self.trailingAnchor
74+
).isActive = true
75+
}
76+
77+
// MARK: - Update
78+
79+
public func update(_ oldModel: AvatarGroupVM) {
80+
guard self.model != oldModel else { return }
81+
82+
let avatarVMs = self.model.identifiedAvatarVMs.map(\.1)
83+
self.addOrRemoveArrangedSubviews(newNumber: avatarVMs.count)
84+
85+
self.stackView.arrangedSubviews.enumerated().forEach { index, view in
86+
(view as? AvatarContainer)?.update(
87+
avatarVM: avatarVMs[index],
88+
groupVM: self.model
89+
)
90+
}
91+
self.style()
92+
}
93+
94+
private func addOrRemoveArrangedSubviews(newNumber: Int) {
95+
let diff = newNumber - self.stackView.arrangedSubviews.count
96+
if diff > 0 {
97+
for _ in 0 ..< diff {
98+
self.stackView.addArrangedSubview(AvatarContainer(avatarVM: .init(), groupVM: self.model))
99+
}
100+
} else if diff < 0 {
101+
for _ in 0 ..< abs(diff) {
102+
if let view = self.stackView.arrangedSubviews.first {
103+
self.stackView.removeArrangedSubview(view)
104+
view.removeFromSuperview()
105+
}
106+
}
107+
}
108+
}
109+
}
110+
111+
// MARK: - Style Helpers
112+
113+
extension UKAvatarGroup {
114+
fileprivate enum Style {
115+
static func stackView(_ view: UIStackView, model: Model) {
116+
view.axis = .horizontal
117+
view.spacing = model.spacing
118+
view.distribution = .equalCentering
119+
}
120+
}
121+
}

0 commit comments

Comments
 (0)