A classic crew-neck sweatshirt. Fabric is heavyweight and soft, knitted in durable premium tightly woven cotton.
Blend
Juice
Roast
Select Color
Select Size
Burbank Peak Trail
Pale green-gold in colour. Aromatic on the nose with notes of greengage and kaffir lime leaf which reflect on the palate. Linear acidity combines with a dry grip, whilst characters of melon and lemongrass endure.
Read More
Burbank Peak Trail
Import engineering excellence
We provide you with a great baseline for designing and building for mobile. By taking care of common use-cases, Moving Parts allows you to move fast, maintain less, and prioritize features that actually matter.
Book a demo1
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
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
import MovingParts
CreditCardLockup(
name: $name,
cardNumber: $cardNumber,
month: $month,
year: $year,
code: $cvc
)
.entryFieldStyle(.rounded)
VStack(alignment: .leading, spacing: 8) {
VStack(alignment: .leading, spacing: 4) {
Text("Cardholder Name")
.font(.caption)
HStack {
Spacer(minLength: 16)
if validationErrors.contains(where: \.isNameError) {
Image(systemName: "exclamationmark.triangle")
.frame(width: iconSize, height: iconSize)
.foregroundColor(.red)
} else {
Image(systemName: "person")
.frame(width: iconSize, height: iconSize)
}
TextField("Jane Doe", text: $name)
.focused($selectedField, equals: .name)
.textContentType(.name)
.disableAutocorrection(true)
}
.frame(minHeight: 54, alignment: .center)
.background(Color(UIColor.tertiarySystemFill))
.cornerRadius(8)
.onTapGesture {
selectedField = .name
}
}
VStack(alignment: .leading, spacing: 4) {
Text("Card Number")
.font(.caption)
HStack {
Spacer(minLength: 16)
if validationErrors.contains(where: \.isCardNumberError) {
Image(systemName: "exclamationmark.triangle")
.frame(width: iconSize, height: iconSize)
.foregroundColor(.red)
} else {
Image(systemName: "creditcard")
.frame(width: iconSize, height: iconSize)
}
let text = Binding<String> {
formattedCardNumberString(from: cardNumber)
} set: { string in
cardNumber = editingCardNumberString(from: string)
}
TextField("4000 0027 6000 0016", text: text)
.focused($selectedField, equals: .cardNumber)
.textContentType(.creditCardNumber)
.autocapitalization(.none)
.disableAutocorrection(true)
.keyboardType(.numberPad)
.transformEnvironment(\.font) { font in
font = (font ?? .body).monospacedDigit()
}
Spacer()
switch issuer {
case .americanExpress?:
Image("American Express")
case .dinersClub?:
Image("Diners Club")
case .discover?:
Image("Discover")
case .jcb?:
Image("JCB")
case .masterCard?:
Image("Mastercard")
case .visa?:
Image("Visa")
case .none:
EmptyView()
}
Spacer(minLength: 16)
}
.frame(minHeight: 54, alignment: .center)
.background(Color(UIColor.tertiarySystemFill))
.cornerRadius(8)
.onTapGesture {
selectedField = .cardNumber
}
.onChange(of: cardNumber) { newValue in
if isCardNumberComplete && isCardNumberValid {
selectedField = .expiryDate
}
}
}
HStack(spacing: 8) {
VStack(alignment: .leading, spacing: 4) {
Text("Expiry Date")
.font(.caption)
HStack {
Spacer(minLength: 16)
if validationErrors.contains(where: \.isExpiryDateError) {
Image(systemName: "exclamationmark.triangle")
.frame(width: iconSize, height: iconSize)
.foregroundColor(.red)
} else {
Image(systemName: "calendar")
.frame(width: iconSize, height: iconSize)
}
let text = Binding<String> {
formattedCardExpiryString(from: cardExpiryTextValue)
} set: { string in
cardExpiryTextValue = editingCardExpiryString(from: string)
(year, month) = yearAndMonthComponent(from: string)
if isExpiryDateComplete(string) {
selectedField = .cvc
}
}
TextField("MM/YY", text: text)
.focused($selectedField, equals: .expiryDate)
.textContentType(.creditCardNumber)
.autocapitalization(.none)
.disableAutocorrection(true)
.keyboardType(.numberPad)
.transformEnvironment(\.font) { font in
font = (font ?? .body).monospacedDigit()
}
Spacer()
}
.frame(minHeight: 54, alignment: .center)
.background(Color(UIColor.tertiarySystemFill))
.cornerRadius(8)
.onTapGesture {
selectedField = .expiryDate
}
}
VStack(alignment: .leading, spacing: 4) {
Text("Security Code")
.font(.caption)
HStack {
Spacer(minLength: 16)
if validationErrors.contains(where: \.isCodeError) {
Image(systemName: "exclamationmark.triangle")
.frame(width: iconSize, height: iconSize)
.foregroundColor(.red)
} else {
Image(systemName: "creditcard.and.123")
.frame(width: iconSize, height: iconSize)
}
TextField(issuer == .americanExpress ? "1234" : "123", text: $cvc)
.focused($selectedField, equals: .cvc)
.textContentType(.creditCardNumber)
.autocapitalization(.none)
.disableAutocorrection(true)
.keyboardType(.numberPad)
.transformEnvironment(\.font) { font in
font = (font ?? .body).monospacedDigit()
}
Button {
showCVCGuide()
} label: {
Image(systemName: "info.circle.fill")
.foregroundColor(.secondary)
.imageScale(.large)
}
Spacer(minLength: 16)
}
.frame(minHeight: 54, alignment: .center)
.background(Color(UIColor.tertiarySystemFill))
.cornerRadius(8)
.onTapGesture {
selectedField = .cvc
}
}
}
VStack(alignment: .leading, spacing: 8) {
ForEach(validationErrors.sorted(), id: \.self) { error in
Text(error.localizedDescription)
.padding(.leading, 10)
.overlay(Capsule().frame(maxWidth: 2, maxHeight: .greatestFiniteMagnitude), alignment: .leading)
.font(.subheadline)
.foregroundColor(.red)
}
}
}
.onChange(of: selectedField) { newValue in
if selectedField == .name {
validationErrors.remove(.incompleteName)
} else if previouslySelectedFields.contains(.name) {
if name.isEmpty {
validationErrors.insert(.incompleteName)
}
}
if selectedField == .cardNumber {
validationErrors.remove(.incompleteCardNumber)
validationErrors.remove(.invalidCardNumber)
} else if previouslySelectedFields.contains(.cardNumber) {
if isCardNumberComplete {
validationErrors.insert(.incompleteCardNumber)
} else if !isCardNumberValid {
validationErrors.insert(.invalidCardNumber)
}
}
if selectedField == .expiryDate {
validationErrors.remove(.invalidExpiryDate)
validationErrors.remove(.incompleteExpiryDate)
} else if previouslySelectedFields.contains(.expiryDate) {
if month == nil || year == nil {
validationErrors.insert(.incompleteExpiryDate)
}
}
if let field = newValue {
previouslySelectedFields.insert(field)
}
}
Styles easily
While our components look great out of the box, they easily adapt to fit your brand’s design language to a tee. Pick and choose from our selection of styles and themes, customize them, or build your own without having to reinvent the wheel.
Localized & Accessible
Our components are pre-localized in all iOS-supported languages, support right-to-left layout, and follow all best practices around barrier-free interaction. With Moving Parts, your app remains compliant and new markets are easy to conquer.
Performance matters
Every bit of performance and quality matters to get to the best possible conversion rate. We focus relentlessly on responsiveness and performance to deliver an optimal experience, even on older devices or slower network connections.
Compose anything mobile
Break apart, piece together, style, iterate, and build experiences that best represent your products and your customers.
Book a demoOutback
Fully native, 100% SwiftUI
By fully leveraging the SwiftUI framework, Moving Parts blends in seamlessly with the rest of your application. There's no new language to learn, no platform to connect to, and no custom build step.
Standard lockups for common layouts
Moving Parts offers turn-key solutions for the most common UX problems. From credit card details to health data, our components achieve high-level goals and never bog you down in details.
Built-in escape hatches
Fully adapting to your brand's design language is extremely important to us. Our components are built with complete customizability in mind to never lock you into an aesthetic.
Make it
your own
Use our components as is or configure them to whatever context you see fit. We make styling easy by allowing you to extend all our components with custom styling or just use one of our included themes.
Book a demoSupported By
Ready to level up your mobile team?
Book a demoArticles
Join our
mailing list
We promise we’ll only send you emails that are actually worthwhile.
Not feeling like more email? Follow us on Mastodon or Twitter.