Smart Home UI In SwiftUI

Mobile Apps Academy
4 min readDec 29, 2023

--

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

  1. TemperatureControllSliderView
  2. AnimatedBackground
  3. 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/

--

--

Mobile Apps Academy

Welcome to Mobile Apps Academy, your go-to channel for all things mobile app development!