{"componentChunkName":"component---src-templates-article-js","path":"/article/swiftui-analogue-watch-design","result":{"data":{"contentfulArticle":{"title":"Analogue watch design","image":"https://res.cloudinary.com/liquidcoder/image/upload/v1597490335/TimeZoneApp/qx2twbtdwuos0du523no.jpg","createdDate":"Thursday, 14 November 2019","videoUrl":"4udnp9_TQ2c","updatedDate":"3 years ago","isNewLayout":null,"previews":null,"slug":"swiftui-analogue-watch-design","author":{"email":"john@liquidcoder.com","name":"John"},"tags":[{"title":"Article"},{"title":"Free"},{"title":"Swift UI"},{"title":"UI Design"}],"allChapters":null,"code":null,"explanation":null,"downloads":[{"name":"Project source code","url":"https://github.com/liquidcoder1/AnalogueWatchDesign/archive/master.zip"}],"resources":[{"name":"Apple developer documentation","url":"https://developer.apple.com/documentation/swiftui"},{"name":"Hacking with swift","url":"https://www.hackingwithswift.com"},{"name":"Swift by sundell","url":"https://www.swiftbysundell.com"}],"body":{"childMdx":{"tableOfContents":{"items":[{"url":"#introduction","title":"Introduction"},{"url":"#requirements","title":"Requirements"},{"url":"#get-started","title":"Get started"},{"url":"#watch-hand","title":"Watch Hand"},{"url":"#watch-frame","title":"Watch Frame"},{"url":"#watch-view","title":"Watch View"},{"url":"#time-logic","title":"Time Logic"}]},"body":"function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }\n\nfunction _objectWithoutProperties(source, excluded) { if (source == null) return {}; var target = _objectWithoutPropertiesLoose(source, excluded); var key, i; if (Object.getOwnPropertySymbols) { var sourceSymbolKeys = Object.getOwnPropertySymbols(source); for (i = 0; i < sourceSymbolKeys.length; i++) { key = sourceSymbolKeys[i]; if (excluded.indexOf(key) >= 0) continue; if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue; target[key] = source[key]; } } return target; }\n\nfunction _objectWithoutPropertiesLoose(source, excluded) { if (source == null) return {}; var target = {}; var sourceKeys = Object.keys(source); var key, i; for (i = 0; i < sourceKeys.length; i++) { key = sourceKeys[i]; if (excluded.indexOf(key) >= 0) continue; target[key] = source[key]; } return target; }\n\n/* @jsxRuntime classic */\n\n/* @jsx mdx */\nvar _frontmatter = {};\nvar layoutProps = {\n  _frontmatter: _frontmatter\n};\nvar MDXLayout = \"wrapper\";\nreturn function MDXContent(_ref) {\n  var components = _ref.components,\n      props = _objectWithoutProperties(_ref, [\"components\"]);\n\n  return mdx(MDXLayout, _extends({}, layoutProps, props, {\n    components: components,\n    mdxType: \"MDXLayout\"\n  }), mdx(\"h2\", null, \"Introduction\"), mdx(\"p\", null, \"Today we are going to design and create an analogue watch in swiftUI. This will be a small piece from an upcoming project I am working on, it\\u2019s a simple app. Writing about the entire thing might be tiresome for readers and me, so I will divide this into smaller pieces. From now on I will be writing about pieces of a project until the entire thing is fully put together.\"), mdx(\"p\", null, \"With that out of the way, let\\u2019s get down to business.\"), mdx(\"h2\", null, \"Requirements\"), mdx(\"ul\", null, mdx(\"li\", {\n    parentName: \"ul\"\n  }, \"Xcode 12 or greater\"), mdx(\"li\", {\n    parentName: \"ul\"\n  }, \"MacOS Catalina or greater\"), mdx(\"li\", {\n    parentName: \"ul\"\n  }, \"Swift 5.0 or greater\"), mdx(\"li\", {\n    parentName: \"ul\"\n  }, \"iOS 14 or greater\")), mdx(\"h2\", null, \"Get started\"), mdx(\"ul\", null, mdx(\"li\", {\n    parentName: \"ul\"\n  }, \"Open Xcode\"), mdx(\"li\", {\n    parentName: \"ul\"\n  }, \"Create a new Xcode project\"), mdx(\"li\", {\n    parentName: \"ul\"\n  }, \"Select single view app and click next\"), mdx(\"li\", {\n    parentName: \"ul\"\n  }, \"Name your app and make sure the user interface is Swift UI\"), mdx(\"li\", {\n    parentName: \"ul\"\n  }, \"Last, click Finish.\"), mdx(\"li\", {\n    parentName: \"ul\"\n  }, \"Before we start, create a folder named \", mdx(\"em\", {\n    parentName: \"li\"\n  }, \"Views\"), \". This is where we will be keeping our views. Let\\u2019s get started.\")), mdx(\"h2\", null, \"Watch Hand\"), mdx(\"p\", null, \"Inside the \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"Views\"), \" folder, add a new file called \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"WatchHand\"), \" and inside that file replace everything inside with the following:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"import SwiftUI\\n\\nstruct WatchHand: View {\\n    var handWidth: CGFloat = 10\\n    var handHeight: CGFloat = 100\\n    var color: Color = Color.blue\\n\\n    var body: some View {\\n        VStack(spacing: -(handWidth / 2)) {\\n            Capsule().frame(width: handWidth, height: handHeight).foregroundColor(color)\\n            Circle().frame(width: handWidth * 2, height: handWidth * 2).foregroundColor(color)\\n        }\\n    }\\n}\\n\\nstruct WatchHand_Previews: PreviewProvider {\\n    static var previews: some View {\\n        WatchHand()\\n    }\\n}\\n\\n\")), mdx(\"p\", null, \"The above code stacks a capsule and circle with a negative spacing to create a watch hand design.\"), mdx(\"p\", null, mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"Preview\")), mdx(\"p\", null, mdx(\"img\", _extends({\n    parentName: \"p\"\n  }, {\n    \"src\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1573763609/TimeZonesApp/g87yjx9kargs55xqxkvh.png\",\n    \"alt\": null\n  }))), mdx(\"h2\", null, \"Watch Frame\"), mdx(\"p\", null, \"Create a new file named \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"WatchFrame\"), \" and replace everything inside with the following:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"import SwiftUI\\n\\nstruct WatchFrame: View {\\n\\n    var size: CGFloat = 200\\n    var borderSize: CGFloat = 20\\n\\n    var body: some View {\\n\\n        ZStack {\\n            Circle()\\n                .frame(width: size, height: size)\\n                  .foregroundColor(Color.clear)\\n                .background(RadialGradient(gradient: Gradient(colors: [Color.white, Color.black]) , center: .center, startRadius: size * 0.41, endRadius: size ) )\\n\\n            Circle().stroke(style: StrokeStyle(lineWidth: borderSize))\\n                .frame(width: size, height: size)\\n                .foregroundColor(Color.white)\\n\\n        }.cornerRadius(size / 2)\\n            .shadow(color: Color.gray.opacity(0.3), radius: 12, x: 0, y: 0)\\n    }\\n}\\n\\nstruct WatchFrame_Previews: PreviewProvider {\\n    static var previews: some View {\\n        WatchFrame()\\n    }\\n}\\n\\n\")), mdx(\"p\", null, \"Notice how we stack 2 circles on the Z axis. The first circle creates an inner shadow by adding a \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"Gradient\"), \" modifier to it and the second creates a border. You can play with the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"startRadius\"), \" value to find what you like if you don\\u2019t like mine.\"), mdx(\"p\", null, mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"Preview\")), mdx(\"p\", null, mdx(\"img\", _extends({\n    parentName: \"p\"\n  }, {\n    \"src\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1573764431/TimeZonesApp/g7xuns9lb28n8mfhu1wc.png\",\n    \"alt\": null\n  }))), mdx(\"h2\", null, \"Watch View\"), mdx(\"p\", null, \"This part will bring the 2 views together to create our analogue  watch. Same as before, creates a new file named \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"WatchView\"), \". At the top of the file add these properties:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"    var diameter: CGFloat = 200\\n    var smallCapsuleHeight: CGFloat = 5\\n    var bigCapsuleHeight: CGFloat = 10\\n\\n    @State var hours: Double = 0.0\\n    @State var minutes: Double = 0.0\\n    @State var seconds: Double = 0.0\\n\\n\")), mdx(\"p\", null, \"Then replace what\\u2019s inside the body block with the following:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"VStack {\\n            Text(\\\"\\\\(hours < 10 ? \\\"0\\\" : \\\"\\\")\\\\(Int(hours)):\\\\(minutes < 10 ? \\\"0\\\" : \\\"\\\")\\\\(Int(minutes)):\\\\(seconds < 10 ? \\\"0\\\" : \\\"\\\")\\\\(Int(seconds))\\\")\\n                           .font(.system(size: 70, weight: .black))\\n        }\\n\")), mdx(\"p\", null, \"Your preview should show this:\"), mdx(\"p\", null, mdx(\"img\", _extends({\n    parentName: \"p\"\n  }, {\n    \"src\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1573767115/TimeZonesApp/mnkuocebznzvvrksbpkr.png\",\n    \"alt\": null\n  }))), mdx(\"p\", null, \"Below the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"body\"), \" block, add this function:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"    func createWatch() -> some View {\\n\\n        let angle = 360 / 60\\n             let hourAngle = 360 / 12\\n             let interval = 360 / angle\\n\\n        let hourLength: CGFloat =  diameter / 2 * 0.4\\n        let minuteLength: CGFloat = diameter / 2 * 0.7\\n        let secondLength: CGFloat = diameter / 2 * 0.8\\n\\n        return  ZStack {\\n            WatchFrame(size: self.diameter + 40, borderSize: 20)\\n\\n            ForEach(0..<interval){ i in\\n                self.createSecondMark(position: i, angle: angle)\\n            }\\n\\n            WatchHand(handWidth: 2, handHeight: hourLength, color: Color.black)\\n                .offset( y:  -(hourLength / 2) + 1.0)\\n                .rotationEffect(Angle(degrees: hours * Double(hourAngle) + (Double(hourAngle) * minutes / 60.0) ) , anchor: .center)\\n\\n            WatchHand(handWidth: 2, handHeight: minuteLength, color: Color.black)\\n                .offset( y:  -(minuteLength / 2) + 1.0)\\n                .rotationEffect(Angle(degrees: minutes * Double(angle) + (Double(angle) * seconds / 60.0)) , anchor: .center)\\n\\n            WatchHand(handWidth: 2, handHeight: secondLength, color: Color.red)\\n                .offset( y:  -(secondLength / 2) + 1.0)\\n                .rotationEffect(Angle(degrees: seconds * Double(angle)) , anchor: .center)\\n        }\\n    }\\n\")), mdx(\"p\", null, \"The above code does the following:\"), mdx(\"ol\", null, mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"Calculates the angle between 2 second marks by dividing the entire circle by 60 which is obviously the number of seconds and we will use the same value for minutes, as for hours, we divide the entire circle by  12.\"), mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"We put everything in a ZStack container because we want them to be stacked on top of each other. First we add the frame, then we create the second marks. You will get an error because the \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"createSecondmark\"), \" does not exist yet.  Last we add the 3 hands (hour, minute and second).\")), mdx(\"p\", null, \"Below the above function, add the following: \"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"\\n    func createSecondMarks(position: Int, angle: Int) -> some View{\\n        if position.isMultiple(of: 5) {\\n            return Capsule().frame(width:  2, height:  self.bigCapsuleHeight).offset(y: self.diameter / 2 - self.bigCapsuleHeight / 2).rotationEffect(Angle(degrees: Double(position * angle) )).foregroundColor(Color.gray)\\n        } else {\\n            return Capsule().frame(width:  1 , height: self.smallCapsuleHeight).offset(y: self.diameter / 2 - self.smallCapsuleHeight / 2).rotationEffect(Angle(degrees: Double(position * angle) )).foregroundColor(Color.gray)\\n        }\\n    }\\n\\n\")), mdx(\"p\", null, \"The above code create the second marks and makes the hour marks a bit thicker and longer than the normal marks. Here the order of modifiers is very important, make sure you write exactly the same code. The code does the following: \"), mdx(\"ol\", null, mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"First give the capsule a frame.\"), mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"Then push it to the edge of the circle by setting its y offset to half the diameter minus its length.\"), mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"And last I set its rotation effect where the angle is the position times the angle we calculated above.\\nNow call the \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"createWatch()\"), \" in the \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"body\"), \" block, above the \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"Text()\"), \" like this: \")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"VStack {\\n            createWatch()\\n            Text(\\\"\\\\(hours < 10 ? \\\"0\\\" : \\\"\\\")\\\\(Int(hours)):\\\\(minutes < 10 ? \\\"0\\\" : \\\"\\\")\\\\(Int(minutes)):\\\\(seconds < 10 ? \\\"0\\\" : \\\"\\\")\\\\(Int(seconds))\\\")\\n                           .font(.system(size: 70, weight: .black))\\n        }\\n\")), mdx(\"p\", null, mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"Preview\")), mdx(\"p\", null, mdx(\"img\", _extends({\n    parentName: \"p\"\n  }, {\n    \"src\": \"https://res.cloudinary.com/liquidcoder/image/upload/v1573802049/TimeZonesApp/hn7j3rkuurjo2cay7ero.png\",\n    \"alt\": \"Swift UI - Analogue watch design\"\n  }))), mdx(\"p\", null, \"The UI Part is done and ready. Let\\u2019s now work on the logic part.\"), mdx(\"h2\", null, \"Time Logic\"), mdx(\"p\", null, \"Below the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"createSecondMarks\"), \" function, add the following code:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \"    func getTimeComponent(from date: Date, format: String) -> Double {\\n        let formatter = DateFormatter()\\n        formatter.dateFormat = format\\n        formatter.locale = Locale.autoupdatingCurrent\\n        let component = formatter.string(from: date)\\n\\n        if let value = Double(component) {\\n            return value\\n        }\\n        return 0.0\\n    }\\n\")), mdx(\"p\", null, \"This function formats and returns a date component in \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"Double\"), \", we will use it to retrieve hours, minutes and seconds from a particular date.\"), mdx(\"p\", null, \"Then add the following modifier to the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"VStack\"), \" container in the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"body\"), \" block:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \".onAppear{\\n            let date = Date()\\n            self.hours = self.getTimeComponent(from: date, format: \\\"HH\\\")\\n            self.minutes = self.getTimeComponent(from: date, format: \\\"mm\\\")\\n            self.seconds = self.getTimeComponent(from: date, format: \\\"ss\\\")\\n\\n            Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { timer in\\n                let date = timer.fireDate\\n\\n                self.hours = self.getTimeComponent(from: date, format: \\\"HH\\\")\\n                self.minutes = self.getTimeComponent(from: date, format: \\\"mm\\\")\\n                self.seconds = self.getTimeComponent(from: date, format: \\\"ss\\\")\\n            }\\n        }\\n\")), mdx(\"p\", null, \"The code inside onAppear will run every time the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"VStack\"), \" first appears and does the following:\"), mdx(\"ol\", null, mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"First get the current date and updates the hands.\"), mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"Then schedules a timer which will be running every second to update the time and move the hands.\\nBefore running the app, make sure you call the \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"WatchView\"), \" inside the \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"ContentView\"), \"\\u2019s body block like this: \")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-swift\"\n  }), \" var body: some View {\\n        WatchView()\\n }\\n\")), mdx(\"p\", null, \"That\\u2019s it folks! Stay tuned for lots of cool stuff like this, don\\u2019t forget to subscribe and share this article.\"));\n}\n;\nMDXContent.isMDXComponent = true;","excerpt":"Introduction Today we are going to design and create an analogue watch in swiftUI. This will be a small piece from an upcoming project I am working on, it’s a simple app. Writing about the entire thing might be tiresome for readers and me, so I will divide this into smaller pieces. From now on I…"}}}},"pageContext":{"slug":"swiftui-analogue-watch-design"}},"staticQueryHashes":["1047155515","1278771865","2517639938","2517639938","2537039069","3452127246","3566879862","3891201089","439617394","670651827"]}