Smart Home UI In SwiftUI
In this article, We will explore how to implement a Home Automation UI in SwiftUI
Before proceeding, please consider subscribing to our YOUTUBE CHANNEL
It gives us a lot of motivation to produce high-quality content for you guys.
if you are interested in watching the video tutorial, Check out below.
In this tutorial, we’ll implement below things
- TemperatureControllSliderView
- AnimatedBackground
- DeviceMenuView
import SwiftUI
struct TempretureControlSliderView: View {
@State var tempretureValue: CGFloat = 0.0
@State var angleValue: CGFloat = 0.0
let config = SliderConfig(
minimumValue: 0.0,
maximumValue: 40.0,
totalValue: 40.0,
knobRadius: 15.0,
radius: 125.0
)
var body: some View {
ZStack {
Circle()
.stroke(style: StrokeStyle(lineWidth: 8, lineCap: .butt, miterLimit: 0, dash: [3, 3], dashPhase: 0))
.foregroundColor(.yellow)
.frame(width: config.radius * 2.5, height: config.radius * 2.5)
Circle()
.foregroundStyle(.white.opacity(0.5))
.frame(width: config.radius * 2.25, height: config.radius * 2.25)
Circle()
.trim(from: 0.0, to: tempretureValue / config.totalValue)
.stroke(tempretureValue < config.maximumValue / 1.1 ? Color.yellow : Color.red, lineWidth: 10)
.frame(width: config.radius * 2, height: config.radius * 2)
.rotationEffect(.degrees(-90))
Circle()
.fill(tempretureValue < config.maximumValue / 1.1 ? Color.white : Color.red)
.overlay {
Circle()
.fill(.gray)
.shadow(radius: 10)
.frame(width: config.knobRadius, height: config.knobRadius)
}
.frame(width: config.knobRadius * 1.5, height: config.knobRadius * 1.5)
.padding(10)
.offset(y: -config.radius)
Circle()
.fill(tempretureValue < config.maximumValue / 1.1 ? Color.white : Color.red)
.overlay {
Circle()
.fill(.gray)
.shadow(radius: 10)
.frame(width: config.knobRadius, height: config.knobRadius)
}
.frame(width: config.knobRadius * 1.5, height: config.knobRadius * 1.5)
.padding(10)
.offset(y: -config.radius)
.rotationEffect(Angle.degrees(Double(angleValue)))
.gesture(
DragGesture(minimumDistance: 0.0)
.onChanged({ value in
change(location: value.location)
})
)
Circle()
.frame(width: config.radius * 1.7, height: config.radius * 1.7)
.foregroundStyle(.white)
.shadow(color: .black, radius: 10)
.overlay {
VStack {
Text("\(String.init(format: "%.0f", tempretureValue)) º C")
.font(.title)
.foregroundStyle(.black)
.bold()
Text("Celcious")
.font(.caption)
.foregroundStyle(.gray)
Text("23 Min Left")
.font(.callout)
.foregroundStyle(.black)
.bold()
}
}
}
}
private func change(location: CGPoint) {
let vector = CGVector(dx: location.x, dy: location.y)
let angle = atan2(vector.dy - (config.knobRadius + 10), vector.dx - (config.knobRadius + 10)) + .pi / 2.0
let fixedAngle = angle < 0.0 ? angle + 2.0 * .pi : angle
let value = fixedAngle / (2.0 * .pi) * config.totalValue
if value >= config.minimumValue && value <= config.maximumValue {
tempretureValue = value
angleValue = fixedAngle * 180 / .pi
}
}
struct SliderConfig {
let minimumValue: CGFloat
let maximumValue: CGFloat
let totalValue: CGFloat
let knobRadius: CGFloat
let radius: CGFloat
}
}
#Preview {
TempretureControlSliderView()
}
import SwiftUI
struct AnimatedBackground: View {
@State private var startAnimation: Bool = true
var body: some View {
ZStack {
Color.black
LinearGradient(
colors: [
.yellow.opacity(0.4),
.black
],
startPoint: startAnimation ? .topLeading : .bottomLeading,
endPoint: startAnimation ? .bottomTrailing : .topTrailing
)
}
.ignoresSafeArea()
}
}
#Preview {
AnimatedBackground()
}
import SwiftUI
var devices = ["AC", "MUSIC", "LIGHTS", "SECURITY"]
struct DeviceMenuView: View {
var devices: [String]
@State private var selectedCategory: Int = 0
var action: (String) -> () // returns the selected item on click
var body: some View {
VStack(alignment: .center) {
GeometryReader{ geo in
ScrollView(.horizontal) {
VStack {
HStack(spacing: 10, content: {
ForEach(0..<devices.count, id: \.self) { i in
DeviceItem(isSelected: i == selectedCategory, title: devices[i])
.onTapGesture {
selectedCategory = i
action(devices[i])
}
}
})
}
.frame(width: geo.size.width)
}.scrollIndicators(.never)
}
}
}
}
#Preview {
DeviceMenuView(devices: devices) { value in
}
}
struct DeviceItem: View {
var isSelected: Bool = false
var title: String = "All"
var body: some View {
VStack(spacing: 0) {
RoundedRectangle(cornerRadius: 10)
.fill(isSelected ? Color.yellow : Color.white)
.shadow(radius: 5)
.overlay {
VStack(spacing: 5){
Image(systemName: getDeviceIcon(deviceName: title))
.resizable()
.aspectRatio(contentMode: .fit)
.foregroundStyle(isSelected ? Color.white : Color.black)
.frame(width: isSelected ? 25 : 15, height: isSelected ? 25 : 15)
Text(title)
.font(.system(size: 10))
.foregroundColor(isSelected ? Color.white : Color.black)
.bold(isSelected)
}
}
.frame(width: 70, height: 70)
}
.padding(5)
}
}
func getDeviceIcon(deviceName: String) -> String {
switch deviceName {
case "AC":
return "air.conditioner.horizontal.fill"
case "MUSIC":
return "opticaldisc.fill"
case "LIGHTS":
return "light.recessed"
case "SECURITY":
return "lock.circle"
default:
return "lock.circle"
}
}
import SwiftUI
struct ContentView: View {
var body: some View {
ZStack {
AnimatedBackground()
VStack {
Title()
DeviceMenuView(devices: devices) { value in
}
.frame(height: 80)
Spacer()
TempretureControlSliderView()
Spacer()
DeviceName()
Button(action: {
}, label: {
RoundedRectangle(cornerRadius: 6)
.fill(.yellow)
.overlay {
Text("SET TEMPRETURE")
.font(.headline)
.foregroundStyle(.black)
.bold()
}
})
.frame(height: 60)
.padding(.horizontal, 20)
}
.frame(maxWidth: .infinity, maxHeight: .infinity)
.padding(.vertical, 50)
}
.ignoresSafeArea()
}
@ViewBuilder
func Title() -> some View {
VStack {
VStack(alignment: .leading, content: {
HStack {
Text("BEDROOM")
.font(.system(size: 28))
.foregroundStyle(.white)
.bold()
Spacer()
}
.padding(.bottom, 10)
HStack {
Text("TOTAL 4 ACTIVE DEVICES")
.font(.system(size: 10))
.foregroundStyle(.yellow)
.bold()
Spacer()
}
})
}
.frame(maxWidth: .infinity)
.padding(.horizontal)
}
@ViewBuilder
func DeviceName() -> some View {
VStack(alignment: .leading){
HStack {
Text("Samsung AC")
.font(.system(size: 28))
.foregroundStyle(.white)
.bold()
Spacer()
}
HStack {
Text("Connected")
.font(.system(size: 15))
.foregroundStyle(.yellow)
.bold()
Spacer()
}
}
.frame(maxWidth: .infinity)
.padding(.horizontal)
.padding(.bottom, 20)
}
}
#Preview {
ContentView()
}
That’s it, you should be able to see the view now.
Once again Thanks for stopping by.
Do check out our YOUTUBE CHANNEL
Social Handles
Instagram : https://www.instagram.com/mobileappsacademy/
Twitter : https://twitter.com/MobileAppsAcdmy
LinkedIn : https://www.linkedin.com/company/mobile-apps-academy/