Skip to content

Commit cf8039d

Browse files
add UKAvatar implementation
1 parent 9a80d71 commit cf8039d

File tree

4 files changed

+134
-1
lines changed

4 files changed

+134
-1
lines changed

Examples/DemosApp/DemosApp/ComponentsPreview/PreviewPages/AvatarPreview.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,10 @@ struct AvatarPreview: View {
99

1010
var body: some View {
1111
VStack {
12+
PreviewWrapper(title: "UIKit") {
13+
UKAvatar(model: self.model)
14+
.preview
15+
}
1216
PreviewWrapper(title: "SwiftUI") {
1317
SUAvatar(model: self.model)
1418
}

Sources/ComponentsKit/Components/Avatar/Models/AvatarVM.swift

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,3 +129,13 @@ extension AvatarVM {
129129
}
130130
}
131131
}
132+
133+
// MARK: - UIKit Helpers
134+
135+
extension AvatarVM {
136+
func shouldUpdateImage(_ oldModel: Self) -> Bool {
137+
return self.imageSrc != oldModel.imageSrc
138+
|| self.placeholder != oldModel.placeholder
139+
|| self.color != oldModel.color
140+
}
141+
}
Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
import UIKit
2+
3+
/// A UIKit component that displays a profile picture, initials or fallback icon for a user.
4+
open class UKAvatar: UIImageView, UKComponent {
5+
// MARK: - Properties
6+
7+
/// A model that defines the appearance properties.
8+
public var model: AvatarVM {
9+
didSet {
10+
self.update(oldValue)
11+
}
12+
}
13+
14+
private var loadedImage: (url: URL, image: UIImage)?
15+
16+
// MARK: - UIView Properties
17+
18+
open override var intrinsicContentSize: CGSize {
19+
return self.sizeThatFits(UIView.layoutFittingExpandedSize)
20+
}
21+
22+
// MARK: - Initializers
23+
24+
/// Initializer.
25+
/// - Parameters:
26+
/// - model: A model that defines the appearance properties.
27+
public init(model: AvatarVM = .init()) {
28+
self.model = model
29+
30+
super.init(frame: .zero)
31+
32+
self.style()
33+
}
34+
35+
public required init?(coder: NSCoder) {
36+
fatalError("init(coder:) has not been implemented")
37+
}
38+
39+
// MARK: - Style
40+
41+
private func style() {
42+
self.contentMode = .scaleToFill
43+
self.clipsToBounds = true
44+
}
45+
46+
// MARK: - Update
47+
48+
public func update(_ oldModel: AvatarVM) {
49+
guard self.model != oldModel else { return }
50+
51+
if self.model.shouldUpdateImage(oldModel) {
52+
self.updateImage()
53+
}
54+
if self.model.cornerRadius != oldModel.cornerRadius {
55+
self.layer.cornerRadius = self.model.cornerRadius.value(for: self.bounds.height)
56+
}
57+
if self.model.size != oldModel.size {
58+
self.setNeedsLayout()
59+
self.invalidateIntrinsicContentSize()
60+
}
61+
}
62+
63+
// MARK: - Layout
64+
65+
open override func layoutSubviews() {
66+
super.layoutSubviews()
67+
68+
self.layer.cornerRadius = self.model.cornerRadius.value(for: self.bounds.height)
69+
70+
self.updateImage()
71+
}
72+
73+
// MARK: - UIView Methods
74+
75+
open override func sizeThatFits(_ size: CGSize) -> CGSize {
76+
let minProvidedSide = min(size.width, size.height)
77+
let minPreferredSide = min(self.model.preferredSize.width, self.model.preferredSize.height)
78+
let side = min(minProvidedSide, minPreferredSide)
79+
return CGSize(width: side, height: side)
80+
}
81+
82+
// MARK: - Helpers
83+
84+
private func downloadImage(url: URL) {
85+
self.loadedImage = nil
86+
Task { @MainActor in
87+
guard let image = await ImageLoader.download(url: url),
88+
url == self.model.imageURL
89+
else { return }
90+
91+
self.loadedImage = (url, image)
92+
UIView.transition(
93+
with: self,
94+
duration: 0.2,
95+
options: .transitionCrossDissolve,
96+
animations: {
97+
self.image = image
98+
}
99+
)
100+
}
101+
}
102+
103+
private func updateImage() {
104+
let size = self.bounds.size
105+
switch self.model.imageSrc {
106+
case .remote(let url):
107+
if let loadedImage, loadedImage.url == url {
108+
self.image = loadedImage.image
109+
} else {
110+
self.image = self.model.placeholderImage(for: size)
111+
self.downloadImage(url: url)
112+
}
113+
case let .local(name, bundle):
114+
self.image = UIImage(named: name, in: bundle, compatibleWith: nil) ?? self.model.placeholderImage(for: size)
115+
case .none:
116+
self.image = self.model.placeholderImage(for: size)
117+
}
118+
}
119+
}

Sources/ComponentsKit/Components/Divider/UKDivider.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ open class UKDivider: UIView, UKComponent {
3232
fatalError("init(coder:) has not been implemented")
3333
}
3434

35-
// MARK: - Setup
35+
// MARK: - Style
3636

3737
private func style() {
3838
self.backgroundColor = self.model.lineColor.uiColor

0 commit comments

Comments
 (0)