├── challenge3.mov ├── images ├── challenge3.png ├── line-graph.png ├── challenge01.png ├── signal-strength-view.png └── signal-strength-indicator.png ├── README.md ├── challenge4.md ├── challenge5.md ├── challenge3.md ├── challenge2.md ├── challenge6.md └── challenge1.md /challenge3.mov: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/objcio/swiftui-challenges/HEAD/challenge3.mov -------------------------------------------------------------------------------- /images/challenge3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/objcio/swiftui-challenges/HEAD/images/challenge3.png -------------------------------------------------------------------------------- /images/line-graph.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/objcio/swiftui-challenges/HEAD/images/line-graph.png -------------------------------------------------------------------------------- /images/challenge01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/objcio/swiftui-challenges/HEAD/images/challenge01.png -------------------------------------------------------------------------------- /images/signal-strength-view.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/objcio/swiftui-challenges/HEAD/images/signal-strength-view.png -------------------------------------------------------------------------------- /images/signal-strength-indicator.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/objcio/swiftui-challenges/HEAD/images/signal-strength-indicator.png -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SwiftUI Challenges 2 | 3 | - [Challenge 1: Badge View](challenge1.md) 4 | - [Challenge 2: Signal Strength Indicator](challenge2.md) 5 | - [Challenge 3: Tab Selection](challenge3.md) 6 | - [Challenge 4: Alignment Guides](challenge4.md) 7 | - [Challenge 5: Animating Paths](challenge5.md) 8 | -------------------------------------------------------------------------------- /challenge4.md: -------------------------------------------------------------------------------- 1 | ![](images/challenge3.png) 2 | 3 | Your challenge is to align a `VStack` of `HStack`s in such a way that it displays as two columns: 4 | 5 | ```swift 6 | VStack { 7 | HStack { 8 | Text("100 m").font(Font.body.bold()) 9 | Text("Usain Bolt") 10 | } 11 | HStack { 12 | Text("5 km").font(Font.body.bold()) 13 | Text("Joshua Cheptegei") 14 | } 15 | HStack { 16 | Text("10 km").font(Font.body.bold()) 17 | Text("Rhonex Kipruto") 18 | } 19 | HStack { 20 | Text("Marathon").font(Font.body.bold()) 21 | Text("Eliud Kipchoge") 22 | } 23 | } 24 | ``` 25 | 26 | Try to achieve this using alignment guides, not using preferences. Of course both columns should adapt to the width of the contents. 27 | 28 | -------------------------------------------------------------------------------- /challenge5.md: -------------------------------------------------------------------------------- 1 | Your challenge is to animate a line. Here's the code that draws a line using a simple path: 2 | 3 | ```swift 4 | let p1 = CGPoint(x: 50, y: 50) 5 | let p2 = CGPoint(x: 100, y: 25) 6 | let p3 = CGPoint(x: 100, y: 100) 7 | 8 | struct ContentView: View { 9 | @State var toggle = true 10 | var body: some View { 11 | VStack { 12 | Button("Toggle") { 13 | withAnimation(.default) { 14 | self.toggle.toggle() 15 | } 16 | } 17 | Path { p in 18 | p.move(to: toggle ? p1 : p2) 19 | p.addLine(to: p3) 20 | }.stroke(lineWidth: 2) 21 | 22 | } 23 | } 24 | } 25 | ``` 26 | 27 | Currently, the line doesn't animate to its new position. Wrap the `Path` in a `Shape` and make sure the start and end points of the line are animatable. 28 | -------------------------------------------------------------------------------- /challenge3.md: -------------------------------------------------------------------------------- 1 | Your challenge is to create a tab-bar like component that has a smooth animation: 2 | 3 | 4 | 5 | This is the interface we expect: 6 | 7 | ```swift 8 | struct TabBar: View { 9 | var items: [(Image, Text)] 10 | @State var selectedIndex: Int = 0 11 | var body: some View { 12 | fatalError("TODO") 13 | } 14 | } 15 | ``` 16 | 17 | That means you should be able to replicate the tab bar in the movie above using the following code: 18 | 19 | ```swift 20 | struct ContentView: View { 21 | var body: some View { 22 | return TabBar(items: [ 23 | (Image(systemName: "tray"), Text("Inbox")), 24 | (Image(systemName: "archivebox"), Text("Archive")), 25 | (Image(systemName: "doc.text"), Text("Drafts")) 26 | ]).padding().border(Color.blue) 27 | } 28 | } 29 | ``` 30 | -------------------------------------------------------------------------------- /challenge2.md: -------------------------------------------------------------------------------- 1 | # Challenge 2: Signal Strength Indicator 2 | 3 | Your task is to create a `SignalStrengthIndicator` view that has a flexible width and height: its contents should adapt to the given frame. Here's a sample of the view in action: 4 | 5 | ```swift 6 | struct ContentView: View { 7 | var body: some View { 8 | SignalStrengthIndicator(bars: 2, totalBars: 5) 9 | .frame(width: 70, height: 30) 10 | } 11 | } 12 | ``` 13 | 14 | It should look like this: 15 | 16 | ![](images/signal-strength-indicator.png) 17 | 18 | You should implement this without using a `GeometryReader`! 19 | 20 | For a bonus challenge, create a `SignalStrengthView` that includes a text label. Make sure that the indicator scales with the environment's current font size. For example, here's the signal strength view with and without a large title font: 21 | 22 | ```swift 23 | struct ContentView: View { 24 | var body: some View { 25 | VStack(spacing: 30) { 26 | SignalStrengthView() 27 | SignalStrengthView() 28 | .font(.largeTitle) 29 | } 30 | } 31 | } 32 | ``` 33 | 34 | ![](images/signal-strength-view.png) 35 | 36 | -------------------------------------------------------------------------------- /challenge6.md: -------------------------------------------------------------------------------- 1 | # Challenge 6: Animating a Line Graph 2 | 3 | In this challenge, your task is to [draw and animate a line graph](https://twitter.com/objcio/status/1237755659610775554). Here's the line graph at different stages of the animation: 4 | 5 | ![](images/line-graph.png) 6 | 7 | Here's a starting point for the code: 8 | 9 | ```swift 10 | let sampleData: [CGFloat] = [0.1, 0.7, 0.3, 0.6, 0.45, 1.1] 11 | 12 | struct LineGraph: Shape { 13 | var dataPoints: [CGFloat] 14 | 15 | func path(in rect: CGRect) -> Path { 16 | // ... 17 | } 18 | } 19 | 20 | struct ContentView: View { 21 | @State var on = true 22 | 23 | var body: some View { 24 | VStack { 25 | LineGraph(dataPoints: sampleData) 26 | .stroke(Color.red, lineWidth: 2) 27 | .aspectRatio(16/9, contentMode: .fit) 28 | .border(Color.gray, width: 1) 29 | .padding() 30 | Button(action: { 31 | withAnimation(Animation.easeInOut(duration: 2)) { 32 | self.on.toggle() 33 | } 34 | }) { Text("Animate") } 35 | } 36 | } 37 | } 38 | ``` 39 | -------------------------------------------------------------------------------- /challenge1.md: -------------------------------------------------------------------------------- 1 | # Challenge 1: Badge View 2 | 3 | In this challenge, we'll add a badge view. When `count` is not zero, it should display a badge in the top trailing corner, like so: 4 | 5 | ![](images/challenge01.png) 6 | 7 | Here's the starting point: 8 | 9 | ```swift 10 | import SwiftUI 11 | 12 | extension View { 13 | func badge(count: Int = 0) -> some View { 14 | self // todo 15 | } 16 | } 17 | 18 | struct ContentView: View { 19 | @State var counter = 0 20 | var body: some View { 21 | VStack { 22 | Stepper(onIncrement: { 23 | self.counter += 1 24 | }, onDecrement: { 25 | self.counter -= 1 26 | }, label: { Text("Count")}) 27 | Text("Hello, World!") 28 | .padding() 29 | .background( 30 | RoundedRectangle(cornerRadius: 5) 31 | .fill(Color.gray)) 32 | .badge(count: counter) 33 | }.padding() 34 | } 35 | } 36 | 37 | struct ContentView_Previews: PreviewProvider { 38 | static var previews: some View { 39 | ContentView() 40 | } 41 | } 42 | 43 | ``` 44 | 45 | ## Solution 46 | 47 | Here's a blog post describing [the solution](https://www.objc.io/blog/2020/02/11/swiftui-challenge-1/). 48 | --------------------------------------------------------------------------------