SwiftUI - How to update a value through a function (with a slider)? - swift

I am new to programming and SwiftUI and I am trying to get my head around this problem:
I have created a simple slider:
Slider(value: $sliderValue, in: 0...100, step: 1)
.padding(8)
.overlay(
Capsule()
.stroke(Color.purple, style: StrokeStyle(lineWidth: 5))
)
I can display the value in a TextView:
Text("\(sliderValue)")
Now what I want to do is create another TextView, which displays the Slider Value with a simple calculation. I can of course just multiplay the sliderValue inside the TextView - that works well.
Text("\(sliderValue * 10)")
But is there a way to put this into another function to be more flexible, especially if the calculation get's more complex? It should still be live updated whenever the slider is dragged.
Here is the full code a bit beautified. Maybe you can explain to me how this works in SwiftUI?
struct ContentView: View {
#State private var sliderValue: Double = 0
var body: some View {
VStack(alignment: .leading) {
VStack(alignment: .leading) {
Text("value".uppercased())
.font(.largeTitle)
.fontWeight(.bold)
Text("\(sliderValue)")
.font(.system(size: 60, weight: .bold))
Text("new value".uppercased())
.font(.largeTitle)
.fontWeight(.bold)
Text("\(sliderValue * 10)") // I'd like to put this into a function somehow!?
.font(.system(size: 60, weight: .bold))
}
Slider(value: $sliderValue, in: 0...100, step: 1)
.padding(8)
.overlay(
Capsule()
.stroke(Color.purple, style: StrokeStyle(lineWidth: 5))
)
}//END: VSTACK
.padding()
}
}
Thanks for your help!

Here is possible approach
struct ContentView: View {
#State private var sliderValue: Double = 0
var body: some View {
VStack(alignment: .leading) {
VStack(alignment: .leading) {
Text("value".uppercased())
.font(.largeTitle)
.fontWeight(.bold)
Text("\(sliderValue)")
.font(.system(size: 60, weight: .bold))
Text("new value".uppercased())
.font(.largeTitle)
.fontWeight(.bold)
self.calculatedView(for: sliderValue)
}
Slider(value: $sliderValue, in: 0...100, step: 1)
.padding(8)
.overlay(
Capsule()
.stroke(Color.purple, style: StrokeStyle(lineWidth: 5))
)
}//END: VSTACK
.padding()
}
private func calculatedView(for value: Double) -> some View {
Text("\(value * 10)")
.font(.system(size: 60, weight: .bold))
}
}
and here is another possible approach
struct ContentView: View {
#State private var sliderValue: Double = 0
var body: some View {
VStack(alignment: .leading) {
VStack(alignment: .leading) {
Text("value".uppercased())
.font(.largeTitle)
.fontWeight(.bold)
Text("\(sliderValue)")
.font(.system(size: 60, weight: .bold))
Text("new value".uppercased())
.font(.largeTitle)
.fontWeight(.bold)
CalculatedView(value: sliderValue)
}
Slider(value: $sliderValue, in: 0...100, step: 1)
.padding(8)
.overlay(
Capsule()
.stroke(Color.purple, style: StrokeStyle(lineWidth: 5))
)
}//END: VSTACK
.padding()
}
}
struct CalculatedView: View {
let value: Double
var body: some View {
Text("\(value * 10)")
.font(.system(size: 60, weight: .bold))
}
}

I'd recommend a computed property just for calculations:
var calculatedValue: Double {
sliderValue * 10
// or some more complex calculations...
}
The calculatedValue will always be up to date with the sliderValue.
Then you can use Asperi's answer to move your TextView to another function or just leave it where it is.
Text("\(calculatedValue)")
.font(.system(size: 60, weight: .bold))

Related

Custom TextField Swiftui

How can I create a text field like the one shown in the figure? with background of that color and white line?
import SwiftUI
#available(iOS 16.0, *)
struct SecondView: View {
#State var nome = ""
#State var cognome = ""
var body: some View {
GeometryReader { geo in
ZStack {
Color(red: 57 / 255, green: 63 / 255, blue: 147 / 255)
Text("Ora tocca ai tuoi dati")
.font(Font.custom("Rubik", size: 22)).foregroundColor(Color.white).font(Font.body.bold()).padding(.bottom, 300).font(Font.headline.weight(.light))
Text("Per iniziare ad usare MyEntZo completa i campi qui sotto").multilineTextAlignment(.center)
.font(Font.custom("Rubik", size: 18)).foregroundColor(Color.white).font(Font.body.bold()).padding(.bottom, 80).font(Font.headline.weight(.light))
TextField("Nome", text: $nome, axis: .vertical)
.textFieldStyle(.roundedBorder)
.background(.ultraThinMaterial)
.padding().padding(.bottom, 20).padding(.top, 110).scaledToFill()
.underline()
TextField("Cognome", text: $nome, axis: .vertical)
.textFieldStyle(.roundedBorder)
.background(.ultraThinMaterial)
.padding().padding(.bottom, 20).padding(.top, 190).scaledToFill()
.underline()
Button(action: {
print("sign up bin tapped")
}){
Text("Continua")
.frame(minWidth: 0, maxWidth: .infinity)
.font(.system(size: 18))
.padding()
.foregroundColor(.white)
.overlay(
RoundedRectangle(cornerRadius: 25)
.stroke(Color.green, lineWidth: 2)
)
}
.background(Color.green)
.cornerRadius(25).frame(width: geo.size.width - 50, height: 100)
.padding(.top, 300)
}
}
.ignoresSafeArea()
}
}
struct SecondView_Previews: PreviewProvider {
static var previews: some View {
SecondView()
}
}
You can create a custom text field that wraps a regular TextField, passing in the #State as a #Binding, e.g.
struct CustomTextField: View {
let placeholder: String
#Binding var text: String
var body: some View {
VStack {
TextField(placeholder, text: $text)
.padding(.bottom, 4)
.foregroundColor(.white)
Color.white
.frame(height: 2)
}
.padding([.top, .bottom, .trailing])
.background(.blue)
}
}
which you can use like:
struct ContentView: View {
#State private var nome: String = ""
var body: some View {
VStack {
// etc
CustomTextField(placeholder: "Nome", text: $nome)
// etc
}
}
}
struct SecondView: View {
#State var nome = ""
#State var cognome = ""
var body: some View {
GeometryReader { geo in
ZStack {
Color(red: 57 / 255, green: 63 / 255, blue: 147 / 255)
VStack {
Spacer()
Text("Ora tocca ai tuoi dati")
.font(Font.custom("Rubik", size: 22))
.foregroundColor(.white)
.bold()
.padding()
.font(Font.headline.weight(.light))
Text("Per iniziare ad usare MyEntZo completa i campi qui sotto").multilineTextAlignment(.center)
.font(Font.custom("Rubik", size: 18)).foregroundColor(Color.white).font(Font.body.bold()).padding(.bottom, 80).font(Font.headline.weight(.light))
TextField("Nome", text: $nome, prompt: Text("Nome").foregroundColor(.white.opacity(0.7)))
.foregroundColor(.white)
.padding(.horizontal)
Rectangle()
.foregroundColor(.white)
.background(.white)
.frame(maxWidth: .infinity, maxHeight: 2)
.padding(.horizontal)
TextField("Cognome", text: $cognome, prompt: Text("Cognome").foregroundColor(.white.opacity(0.7)))
.foregroundColor(.white)
.padding([.horizontal, .top])
Rectangle()
.foregroundColor(.white)
.background(.white)
.frame(maxWidth: .infinity, maxHeight: 2)
.padding(.horizontal)
Button(action: {
print("sign up bin tapped")
}){
Text("Continua")
.frame(minWidth: 0, maxWidth: .infinity)
.font(.system(size: 18))
.padding()
.foregroundColor(.white)
.overlay(
RoundedRectangle(cornerRadius: 25)
.stroke(Color.green, lineWidth: 2)
)
}
.background(Color.green)
.cornerRadius(25).frame(width: geo.size.width - 50, height: 100)
.padding(.top)
Spacer()
}
}
}
.ignoresSafeArea()
}
}

Zoom Image over top of other Views SwiftUI

I have a image as thumbnail when image is tapped it should be expanded/zoom at the centre of screen with background as blur. I tried scale effect but the image is not on top of other view but looks like behind (see pics). How to achieve this zoomed imaged with blur background effect (see peacock pic this is the requirement)
#State var enlarge:Bool = false
var body: some View {
GeometryReader{geo in
VStack{
ZStack(alignment: .top){
LinearGradient(gradient:Gradient(colors: [.blue.opacity(0.3),.blue.opacity(0.2)]),startPoint: .top,endPoint:.bottom)
.ignoresSafeArea()
VStack(alignment: .leading,spacing :5){
HStack{
Text("Lorum Ipsum ackndweg")
.fontWeight(.semibold)
.padding(.top,15)
.padding(.leading,18)
.foregroundColor(ThemeColor.testName)
}
.frame(width: geo.size.width, alignment: .leading)
Image("capAm")
.resizable()
.scaledToFit()
.frame(width: 40, height: 40)
.padding(.leading,18)
.onTapGesture{
withAnimation
{
self.enlarge.toggle()
}
}
.scaleEffect(self.enlarge ? 4 : 1,anchor: .topLeading)
VStack(alignment:.leading,spacing: 5){
HStack{
Text("Turn Around Time :")
.font(.system(size: 14))
.foregroundColor(.red)
Text("Report Delivery : Daily")
.font(.system(size: 14))
.foregroundColor(.orange)
}
.frame(width: geo.size.width, alignment: .center)
VStack(alignment:.leading)
{
HStack{
Text("Turn Around Time(TAT) :")
.font(.system(size: 14))
.foregroundColor(.red)
Text("4 hours after acceptance of the sample at the centre")
.font(.system(size: 14))
.foregroundColor(.red)
.multilineTextAlignment(.leading)
}
}.frame(width: geo.size.width, alignment: .center)
}
}}}}}}
below just an idea
struct SwiftUIView: View {
#State private var enlarge = false
#State private var list = 1...10
#State private var current = 0
var body: some View {
ZStack {
ZStack {
Image(systemName: "\(current).circle")
.resizable()
.scaledToFit()
.frame(width: 60 , height: 60)
.padding()
.animation(.spring(), value: enlarge)
.foregroundColor(.yellow)
}
.frame(width: 300,
height: 200)
.background(Color.black.opacity(0.2))
.foregroundColor(Color.clear)
.cornerRadius(20)
.transition(.slide)
.opacity(self.enlarge ? 1 : 0)
.zIndex(2)
.onTapGesture{
withAnimation {
self.enlarge.toggle()
}
}
List {
ForEach(list, id:\.self) { i in
Label("detail of \(i)", systemImage: "\(i).circle")
.onTapGesture{
current = i
withAnimation {
self.enlarge.toggle()
}
}
}
}
.blur(radius: self.enlarge ? 3 : 0).offset(y: 1)
}
.onTapGesture{
withAnimation {
self.enlarge = false
}
}
}
}

Autolayout in SwiftUI

Good afternoon community,
Any way to autolayout in swiftui so that my view looks good both in portrait and landscape?
I've already tried everything with geometry render and frame (.infinity) but I can't get it.
someone who has managed to be able to have his ap in both modes with swuftui?
I attach my code and a couple of images.
import SwiftUI
struct OnBoardingView: View {
var ImageOnTop:some View{
Image("headerPicture")
.resizable()
}
var Title:some View {
Text("Hey You")
.font(.custom("Montserrat-Medium", size: 48))
.foregroundColor(.white)
}
var subTitle:some View{
Text("Out With A Porpuse ")
.font(.custom("Montserrat-SemiBold", size: 16))
.foregroundColor(.white)
}
var secondSubtitle:some View{
Text("Find and build communities with people in your area. \n #GoodbyeCatfish \n #HellowConnections")
.font(.custom("Montserrat-Regular", size: 16))
.foregroundColor(.white)
.multilineTextAlignment(.center)
}
var navigatinButton:some View{
VStack(alignment:.center){
NavigationLink(
destination: CreateAccountView(),
label: {
Text("Create Account")
})
.font(.custom("Montserrat-Medium", size: 18))
.foregroundColor(.white)
.frame(width: 230, height: 42, alignment: .center)
.overlay(RoundedRectangle(cornerRadius: 18)
.stroke(Color.yellow, lineWidth: 1.8))
HStack{
Text("Already have an account?")
.font(.custom("Montserrat-Regular", size: 12))
.foregroundColor(.white)
.multilineTextAlignment(.leading)
NavigationLink(
destination: LoginView(LoginViewM: LoginViewModel()),
label: {
Text("Log In")
})
.font(.custom("Montserrat-Regular", size: 12))
.foregroundColor(.yellow)
}
}
}
var lastString: some View{
HStack{
Text("By using this app you agree with the")
.foregroundColor(.white)
.font(.custom("Montserrat-Medium", size: 11))
Text("terms of services")
.underline(true, color: .yellow)
.foregroundColor(.yellow)
.font(.custom("Montserrat-Medium", size: 11))
}
.padding(30)
}
var body: some View {
GeometryReader{ geometry in
NavigationView{
VStack(spacing:40){
VStack(alignment: .center,spacing:15){
ImageOnTop
.frame(width: geometry.size.width, height: 270)
Title
subTitle
}
.frame(width: geometry.size.width, height: 270)
secondSubtitle
.frame(width: geometry.size.width, height: 75, alignment: .center)
.padding(30)
navigatinButton
Spacer(minLength: 14)
lastString
Spacer().frame(height:20)
}
.edgesIgnoringSafeArea(.all)
.background(Color.black)
}
.frame(width: geometry.size.width, height: geometry.size.height)
.navigationBarBackButtonHidden(true)
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
OnBoardingView()
.previewDevice(PreviewDevice(rawValue: "iPhone 12"))
.previewDisplayName("iPhone 12")
}
}
Here is a version of your code that works for all screen sizes and positions. Less is more in SwiftUI. The more the hardcoded values the harder it is for SwiftUI to do the adjusting.
struct OnBoardingView: View {
var ImageOnTop:some View{
//Just to simulate,I switched to a system image you will have to adjust for your own
Image(systemName: "square")
.resizable()
.foregroundColor(.blue)
}
var Title:some View {
Text("Hey You")
.font(.custom("Montserrat-Medium", size: 48))
.foregroundColor(.white)
}
var subTitle:some View{
Text("Out With A Porpuse ")
.font(.custom("Montserrat-SemiBold", size: 16))
.foregroundColor(.white)
}
var secondSubtitle:some View{
VStack{
Text("Find and build communities with people in your area.")
.lineLimit(1)
Text("#GoodbyeCatfish \n #HellowConnections")
}
.font(.custom("Montserrat-Regular", size: 16))
.foregroundColor(.white)
.multilineTextAlignment(.center)
.minimumScaleFactor(0.5)
}
var navigatinButton:some View{
VStack(alignment:.center){
NavigationLink(
destination: Text("CreateAccountView()"),
label: {
Text("Create Account")
})
.font(.custom("Montserrat-Medium", size: 18))
.foregroundColor(.white)
//Don't fix the size or thre won't be any differences between devices
.frame(minWidth: 0, idealWidth: 230, maxWidth: 230, minHeight: 0, idealHeight: 42, maxHeight: 42, alignment: .center)
.overlay(RoundedRectangle(cornerRadius: 18)
.stroke(Color.yellow, lineWidth: 1.8))
HStack{
Text("Already have an account?")
.font(.custom("Montserrat-Regular", size: 12))
.foregroundColor(.white)
.multilineTextAlignment(.leading)
NavigationLink(
destination: Text("LoginView(LoginViewM: LoginViewModel()"),
label: {
Text("Log In")
})
.font(.custom("Montserrat-Regular", size: 12))
.foregroundColor(.yellow)
}
}
}
var lastString: some View{
HStack{
Text("By using this app you agree with the")
.foregroundColor(.white)
.font(.custom("Montserrat-Medium", size: 11))
Text("terms of services")
.underline(true, color: .yellow)
.foregroundColor(.yellow)
.font(.custom("Montserrat-Medium", size: 11))
}
.padding(30)
}
var body: some View {
GeometryReader{ geometry in
NavigationView{
VStack{
ImageOnTop
.frame(height: geometry.size.height * 0.2)
VStack{
Title
subTitle
}.frame(height: geometry.size.height * 0.25)
Spacer()
secondSubtitle
//You can give spaces more weight by addign spacers
//This makes the space above the button twice as wide as the bottom
Spacer()
Spacer()
navigatinButton
Spacer()
lastString
}
.background(Color.black)
.edgesIgnoringSafeArea(.all)
.navigationBarHidden(true)
}
.navigationBarBackButtonHidden(true)
}
}
}

A View.environmentObject(_:) for AppInformation may be missing as an ancestor of this view

So I'm currently getting to grips with swift and swiftui and have run into a problem with Observable objects. I want to be able to share the value of a variable over 2 different views so opted to use an Observable Object. The duration and interval variables will be set using a slider on the settings view, which will then be used for logic in my main view.
struct Settings: View {
#EnvironmentObject var appInfo: AppInformation
var body: some View {
NavigationView {
ZStack {
VStack (spacing: 35){
HStack {
Text("Duration")
.font(.system(size: 23, weight: .bold, design: .rounded))
.padding(.leading, 25)
Spacer()
Slider(value: appInfo.duration, in: 1...60, step: 1)
.padding(.trailing, 20)
.padding(.leading, 20)
Text("\(Int(appInfo.duration))")
.padding(.trailing, 30)
.font(.system(size: 23, weight: .medium, design: .rounded))
}
.padding(.top, 100)
HStack {
Text("Interval ")
.font(.system(size: 23, weight: .bold, design: .rounded))
.padding(.leading, 25)
Spacer()
Slider(value: appInfo.interval, in: 1...60, step: 1)
.padding(.trailing, 20)
.padding(.leading, 20)
Text("\(Int(appInfo.interval))")
.padding(.trailing, 30)
.font(.system(size: 23, weight: .medium, design: .rounded)).navigationTitle("Settings")
}
Spacer()
}
}
}
}
struct Settings_Previews: PreviewProvider {
static var previews: some View {
Settings()
.environmentObject(AppInformation())
}
}
}
class AppInformation: ObservableObject {
var duration = 0.0
var interval = 0.0
}
But when attempting to either preview or sun my app in the simulator, it throws an error 'A View.environmentObject(_:) for AppInformation may be missing as an ancestor of this view.'
It throws the error on these lines where I'm referencing the variable as a slider value, even though I have referenced the variable in a text view further down which didn't seem to have any problems.
.padding(.trailing, 20)
.padding(.leading, 20)
Text("\(Int(appInfo.duration))")
.padding(.trailing, 30)
.font(.system(size: 23, weight: .medium, design: .rounded))
Slider(value: appInfo.interval, in: 1...60, step: 1)
.padding(.trailing, 20)
.padding(.leading, 20)
So first of all, your example code is not working.
Use #Published for your properties, then use Slider(value: $appInfo.duration, ...). This is because Slider is expecting a Binding (see https://developer.apple.com/documentation/swiftui/slider)
Use the struct Settings_Previews outside of struct Settings
Below you will find the fixed version. Both running in previews and in Simulator.
To answer your question:
If you use Settings anywhere in your Code, you have to pass it an environmentObject of AppInformation (See ContentView). Since you did not provide any source code in context I assume thats where your error is.
Here is a working example:
import SwiftUI
import CoreData
class AppInformation: ObservableObject {
#Published var duration = 0.0
#Published var interval = 0.0
}
struct Settings: View {
#EnvironmentObject var appInfo: AppInformation
var body: some View {
NavigationView {
ZStack {
VStack (spacing: 35){
HStack {
Text("Duration")
.font(.system(size: 23, weight: .bold, design: .rounded))
.padding(.leading, 25)
Spacer()
Slider(value: $appInfo.duration, in: 1...60, step: 1)
.padding([.trailing, .leading], 20)
Text("\(Int(appInfo.duration))")
.padding(.trailing, 30)
.font(.system(size: 23, weight: .medium, design: .rounded))
}
.padding(.top, 100)
HStack {
Text("Interval ")
.font(.system(size: 23, weight: .bold, design: .rounded))
.padding(.leading, 25)
Spacer()
Slider(value: $appInfo.interval, in: 1...60, step: 1)
.padding([.trailing, .leading], 20)
Text("\(Int(appInfo.interval))")
.padding(.trailing, 30)
.font(.system(size: 23, weight: .medium, design: .rounded))
}
Spacer()
}
}
.navigationTitle("Settings")
}
}
}
struct Settings_Previews: PreviewProvider {
static var previews: some View {
Settings()
.environmentObject(AppInformation())
}
}
struct ContentView: View {
#ObservedObject var appInformation = AppInformation()
var body: some View {
Settings()
.environmentObject(appInformation)
}
}
Also here are some tips using SwiftUI:
If you need to set padding on multiple edges you can use also use .padding([.trailing, .leading], 20)
You should set the .navigationTitle("Settings") modifier on the outer child of the view - but within the NavigationView
You can also set the .font modifier on the VStack or HStack, this would apply it to all children (including the Slider Label)

Sizing multiple vstack width in scroll view, SwiftUI

I have a VStack:
VStack(alignment: .leading) {
Text(question)
.fontWeight(.heavy)
.font(.system(.footnote, design: .rounded))
.padding(.bottom)
Text(answer)
.fontWeight(.semibold)
.font(.system(.title, design: .rounded))
}
.padding(35)
.overlay(RoundedRectangle(cornerRadius: 12).stroke(Color("darkGrey"), lineWidth: 2))
.padding([.leading,.top,.trailing])
.frame(minWidth: UIScreen.main.bounds.width - 5,
minHeight: 50,
maxHeight: .infinity,
alignment: .topLeading
)
And Im using it in my View like:
ScrollView(.horizontal, showsIndicators: false) {
Group {
HStack {
questionCard(question: "Cats or Dogs", answer: "Dogs")
questionCard(question: "Cats or Dogs", answer: "Dogs")
}
}
}
This results in the following:
I'm trying to get the width of the box to the end of the screen (with .trailing space) and then when I scroll to the next, it should be the same width.
You could do something like the following:
struct ContentView: View {
var body: some View {
ScrollView(.horizontal, showsIndicators: false) {
Group {
HStack {
QuestionCard(question: "Cats or Dogs", answer: "Dogs")
QuestionCard(question: "Cats or Dogs", answer: "Dogs")
}
}
}
}
}
struct QuestionCard: View {
let question: String
let answer: String
var body: some View {
HStack {
Spacer()
VStack(alignment: .leading) {//<- Change the alignment if needed
Text(question)
.fontWeight(.heavy)
.font(.system(.footnote, design: .rounded))
.padding(.bottom)
Text(answer)
.fontWeight(.semibold)
.font(.system(.title, design: .rounded))
}
Spacer()
}
.padding(35)
.overlay(RoundedRectangle(cornerRadius: 12).stroke(Color.black, lineWidth: 2))
.padding([.leading, .top, .trailing])
.frame(minWidth: UIScreen.main.bounds.width - 5,
minHeight: 50,
maxHeight: .infinity,
alignment: .top
)
}
}
So basically I`m just using an HStack and two Spacers in order to use all of the available space.
It looks like this: