I'm new to Swift at the moment and I'm having trouble appending to a to-do list. I am not receiving an error, so I don't know what's wrong. I hope this isn't too much code to go through, but I don't even know where the problem is.
There is a sheet modal that opens and closes fine. The only issue is that when I press the save button, the information I typed in doesn't add to the list.
I've add the ViewModel, NewTaskView(sheet), TaskView(customizes list), and a bit of code of the DetailView where the list should be.
import Foundation
import SwiftUI
class TaskViewModel : Identifiable , ObservableObject {
#Published var tasks : [Task] = [
Task(taskName: "Lab", taskDate: Date(), taskCompleted: false),
Task(taskName: "Assignment 4.02", taskDate: Date(), taskCompleted: false)
]
#Published var sortType : SortType = .alphabetical
#Published var isPresented = false
#Published var searched = ""
func addTask(task : Task){
tasks.append(task)
}
func removeTask(indexAt : IndexSet){
tasks.remove(atOffsets: indexAt)
}
func sort(){
switch sortType {
case .alphabetical :
tasks.sort(by: { $0.taskName < $1.taskName })
case .date :
tasks.sort(by: { $0.taskDate > $1.taskDate })
}
}
}
struct Task : Identifiable , Equatable {
var id : String = UUID().uuidString
let taskName : String
let taskDate : Date
var taskCompleted: Bool
}
enum SortType : String , Identifiable , CaseIterable {
var id : String { rawValue }
case alphabetical
case date
}
struct NewTaskView: View {
#Environment(\.presentationMode) var presentationMode
#ObservedObject var taskVM : TaskViewModel
#State var taskName = ""
#State var taskCompleted = Bool()
#State var taskDate = Date()
var body: some View {
NavigationView {
VStack(spacing: 14) {
Spacer()
TextField("Assignment Name",text: $taskName)
.padding()
.background(Color("tan"))
.cornerRadius(5)
.font(Font.custom(FontNameManager.Montserrat.semibold, size: 15))
.padding(.bottom, 20)
HStack{
Text("Due Date")
.font(Font.custom(FontNameManager.Montserrat.semibold, size: 15))
Image(systemName: "bell.badge")
.foregroundColor(.gray)
.frame(width: 30, height: 30, alignment: .leading)
VStack{
DatePicker("Select Date", selection: $taskDate, displayedComponents: .date)
.labelsHidden()
}
}
HStack{
Text("Mark as Completed")
.font(Font.custom(FontNameManager.Montserrat.semibold, size: 15))
Button(action : {
taskCompleted.toggle()
}){
if taskCompleted == true {
Image(systemName: "checkmark.circle.fill")
.foregroundColor(Color("orange"))
.font(.title2)
}
else {
Image(systemName: "circle")
.foregroundColor(Color("lightorange"))
.font(.title2)
}
}
}
Spacer()
Button(action:{taskVM.addTask(task: .init(taskName: taskName, taskDate: taskDate, taskCompleted: taskCompleted))
presentationMode.wrappedValue.dismiss()},
label:{
Text("Save")
})
}.padding()
.navigationBarItems(trailing: Button(action: {
presentationMode.wrappedValue.dismiss()
}) {
HStack{
Image(systemName: "xmark")
.foregroundColor(Color("orange"))
}
})
}
}
}
struct NewTaskView_Previews: PreviewProvider {
static var previews: some View {
NewTaskView(taskVM: TaskViewModel())
}
}
struct TaskView: View {
var task : Task
#Environment(\.managedObjectContext) var moc
#State var currentDate: Date = Date()
#State var taskCompleted = false
func getDateFormatString(date:Date) -> String
{
var dateString = ""
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "MMM. dd"
dateString = dateFormatter.string(from: date)
return dateString
}
var body: some View {
HStack {
Button(action : {
self.taskCompleted.toggle()
try? self.moc.save()
}){
if self.taskCompleted {
Image(systemName: "checkmark.circle.fill")
.foregroundColor(Color("orange"))
.font(.title2)
}
else {
Image(systemName: "circle")
.foregroundColor(Color("lightorange"))
.font(.title2)
}
}.padding()
VStack (alignment: .leading, spacing: 2){
Text("\(task.taskName)")
.font(Font.custom(FontNameManager.Montserrat.bold, size: 18))
.foregroundColor(Color("dark"))
.padding([.leading, .trailing], 1)
if self.taskCompleted {
Text("Completed")
.font(Font.custom(FontNameManager.Montserrat.bold, size: 12))
.foregroundColor(Color("burntorange"))
.frame(width: 95, height: 20)
.background(Color("lightorange"))
.cornerRadius(4)
}
else if currentDate > task.taskDate {
Text("Late")
.font(Font.custom(FontNameManager.Montserrat.semibold, size: 12))
.foregroundColor(.white)
.frame(width: 95, height: 18)
.background(Color("lightbrown"))
.cornerRadius(4)
}
else {
let diffs = Calendar.current.dateComponents([.day], from: currentDate, to: task.taskDate )
Text("\(diffs.day!) days left")
.font(Font.custom(FontNameManager.Montserrat.semibold, size: 12))
.foregroundColor(Color("burntorange"))
.frame(width: 95, height: 20)
.background(Color("lightorange"))
.cornerRadius(4)
}
}
Spacer()
Text(getDateFormatString(date: task.taskDate ))
.font(Font.custom(FontNameManager.Montserrat.medium, size: 12))
.padding()
}
.padding(10)
.cornerRadius(10)
.background(
RoundedRectangle(cornerRadius: 10 , style: .continuous)
.foregroundColor(.white))
}
}
struct TaskView_Previews: PreviewProvider {
static var previews: some View {
TaskView(task: Task(id: "", taskName: "Task Name", taskDate: Date(), taskCompleted: false))
}
}
struct DetailsView: View {
#Environment(\.managedObjectContext) var moc
#ObservedObject var developers : Developer
#ObservedObject var taskVM : TaskViewModel
#Environment(\.presentationMode) var presen
#Environment(\.editMode) var editButton
#State var taskCompleted = false
#State var show = false
#State var showSearch = false
#State var txt = ""
#State var currentDate: Date = Date()
#State var image : Data = .init(count: 0)
#State var indices : [Int] = []
func getDateFormatString(date:Date) -> String
{
var dateString = ""
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "MMM. dd"
dateString = dateFormatter.string(from: date)
return dateString
}
var body: some View {
NavigationView {
ZStack {
ZStack{
VStack{
Color("lightorange")
.clipShape(CustomCorners(corners: [.bottomLeft, .bottomRight], size: 70))
.ignoresSafeArea(.all, edges: .top)
.frame(width: 420, height: 175)
Spacer()
}
VStack{
HStack {
if !self.showSearch{
Button(action: {
withAnimation(.spring()){
self.presen.wrappedValue.dismiss()
}
}) {
Image(systemName: "chevron.left")
.font(.title2)
.foregroundColor(Color("dark"))
.padding(10)
}
}
Spacer(minLength: 0)
HStack {
if self.showSearch{
Image(systemName: "magnifyingglass")
.font(.title2)
.foregroundColor(Color("dark"))
.padding(.horizontal, 8)
TextField("Search", text: $taskVM.searched , onEditingChanged: { (isBegin) in
if isBegin {
showSearch = true
} else {
showSearch = false
}
})
Button(action: {
withAnimation{
self.showSearch.toggle()
}
}) {
Image(systemName: "xmark").foregroundColor(.black).padding(.horizontal, 8)
}
}
else {
Button(action: {
withAnimation {
self.showSearch.toggle()
}
}) {
Image(systemName: "magnifyingglass")
.font(.title2)
.foregroundColor(Color("dark"))
.padding(10)
}
}
}.padding(self.showSearch ? 10 : 0)
.background(Color("lightorange"))
.cornerRadius(20)
}
ZStack{
RoundedRectangle(cornerRadius: 25)
.foregroundColor(Color("tan"))
.frame(width: 335, height: 130)
HStack {
VStack (alignment: .leading){
// Image(uiImage: UIImage(data: developers.imageD ?? self.image)!)
// .resizable()
// .frame(width: 70, height: 70)
// .clipShape(RoundedRectangle(cornerRadius: 20))
Text("\(developers.username ?? "")")
.font(Font.custom(FontNameManager.Montserrat.bold, size: 24))
.foregroundColor(Color("dark"))
Text("\(developers.descriptions ?? "")")
.font(Font.custom(FontNameManager.Montserrat.semibold, size: 18))
.foregroundColor(.gray)
}
.padding([.leading, .trailing], 70)
.padding(.bottom, 80)
Spacer()
}
}
HStack{
Text("Assignments")
.font(Font.custom(FontNameManager.Montserrat.bold, size: 20))
Spacer(minLength: 0)
EditButton()
}
.padding([.leading, .trailing], 20)
Spacer()
List {
ForEach (taskVM.tasks.filter {
self.taskVM.searched.isEmpty ? true : $0.taskName.localizedCapitalized.contains(self.taskVM.searched)} ){ task in
TaskView(task: task)
.listRowSeparator(.hidden)
}
.onDelete(perform: {
taskVM.removeTask(indexAt: $0)
})
}
.listStyle(InsetListStyle())
}
}
VStack {
Spacer()
HStack {
Spacer()
ExpandableFAB(taskVM: TaskViewModel(), show: $show)
}
}
}.navigationBarHidden(true)
.navigationBarBackButtonHidden(true)
}
}
}
struct DetailsView_Previews: PreviewProvider {
static let persistenceController = PersistenceController.shared
static var developers: Developer = {
let context = persistenceController.container.viewContext
let developers = Developer(context: context)
developers.username = "Math"
developers.descriptions = "Calculus"
return developers
}()
static var previews: some View {
DetailsView(developers: developers, taskVM: TaskViewModel()).environment(\.managedObjectContext, persistenceController.container.viewContext)
}
}
struct ExpandableFAB: View {
#Environment(\.managedObjectContext) var moc
#ObservedObject var taskVM : TaskViewModel
#Binding var show: Bool
var body: some View{
VStack(spacing: 20){
if self.show{
Button(action: {
taskVM.isPresented.toggle()
}) {
Image(systemName: "list.bullet.rectangle.portrait").resizable().frame(width: 25, height: 25).padding()
}
.foregroundColor(.white)
.background(Color("orange"))
.clipShape(Circle())
Button(action: {
self.show.toggle()
}) {
Image(systemName: "doc.badge.plus").resizable().frame(width: 25, height: 25).padding()
}.foregroundColor(.white)
.background(Color("orange"))
.clipShape(Circle())
}
Button(action: {
self.show.toggle()
}) {
Image(systemName: "plus").resizable().frame(width: 25, height: 25).padding()
}.foregroundColor(.white)
.background(Color("orange"))
.clipShape(RoundedRectangle(cornerRadius: 20))
.padding()
.rotationEffect(.init(degrees: self.show ? 180 : 0))
}.fullScreenCover(isPresented: $taskVM.isPresented, content: {
NewTaskView(taskVM: taskVM)
})
}
}
#burnsi I don't think I have that anywhere. Where should there be one?
Of course you have one and you need one:
ExpandableFAB(taskVM: TaskViewModel(), show: $show)
This line is causing the issue.
You are somehow injecting a TaskViewModel in your DetailsView (You are not showing where), but not using it. Try:
ExpandableFAB(taskVM: taskVM, show: $show)
And the place you create your ViewModel ('TaskViewModel()') should have a #StateObject wrapper.
Explanation:
As you add something to your ViewModel the views depending on the ViewModel get rebuild. So your TaskViewModel ends up beeing recreated and you still have your 2 initial values in your array.
I'm trying to conditionally render two Forms based on the selection from a segmented picker. It all works fine when there's no animations, but the moment I add them in, the first Form seems to break.
Code for the 1st Form:
struct CalculateView: View {
#Binding var checkAmount: Double
#Binding var numberOfPeople: Int
#Binding var tipPercentage: Int
#Binding var currentMode: DisplayMode
#FocusState var amountIsFocused: Bool
let tipPercentages: [Int]
var currencyType: FloatingPointFormatStyle<Double>.Currency
var grandTotal: Double
var totalPerPerson: Double
var body: some View {
Form{
Section{
TextField("Amount", value: $checkAmount, format:
.currency(code: Locale.current.currencyCode ?? "USD"))
.keyboardType(.decimalPad)
.focused($amountIsFocused)
Picker("Number of people", selection: $numberOfPeople) {
ForEach(1..<100, id: \.self){
Text($0 == 1 ? "\($0) Person" : "\($0) People")
}
}
}
Section{
Picker("Tip Percentage", selection: $tipPercentage){
ForEach(0..<101){
Text($0, format: .percent)
}
}.pickerStyle(.wheel)
} header: {
Text("How much tip do you want to leave ?")
}
Section{
HStack {
Text("Grand Total")
.foregroundColor(.secondary)
.font(.system(size:14, weight: .regular))
.textCase(.uppercase)
Spacer()
Text(grandTotal, format:
currencyType)
.foregroundColor(tipPercentage == 0 ? .red: .blue)
.font(.system(size: 20, weight: .regular))
}.padding(.horizontal, 10)
VStack(alignment: .leading){
Text("Amount per person")
.foregroundColor(.secondary)
.font(.system(size: 15, weight: .semibold))
.padding(EdgeInsets(top: 5, leading: 10, bottom: 0, trailing: 0))
.textCase(.uppercase)
Text(totalPerPerson, format:
.currency(code: Locale.current.currencyCode ?? "USD"))
.frame(maxWidth: .infinity)
.font(.system(size: 55, weight: .thin))
.padding(EdgeInsets(top: 15, leading: 0, bottom: 20, trailing: 0))
.foregroundColor(.green)
}
} header: {
Text("Final Payment")
.foregroundColor(.orange)
}
}
}
}
Code for the 2nd Form:
struct HistoryView: View {
var body: some View {
Form {
Section {
Text("test")
}
}
}
}
Main ContentView code:
enum DisplayMode: String, Equatable, CaseIterable {
case calculate
case history
}
struct ContentView: View {
#State private var checkAmount = 0.0
#State private var numberOfPeople = 2
#State private var tipPercentage = 20
#State private var currentMode: DisplayMode = .calculate
#FocusState private var amountIsFocused: Bool
let tipPercentages = [10, 15, 20, 25, 0]
var currencyType: FloatingPointFormatStyle<Double>.Currency {
return .currency(code: Locale.current.currencyCode ?? "USD")
}
var grandTotal: Double {
let tipValue = checkAmount / 100 * Double(tipPercentage)
let total = checkAmount + tipValue
return total
}
var totalPerPerson: Double {
let peopleCount = Double(numberOfPeople)
let amountPerPerson = grandTotal / peopleCount
return amountPerPerson
}
var navigationTitle: String {
let title = currentMode == .calculate ? "WeSplit" : currentMode.rawValue.capitalized
return title
}
let swapRight: AnyTransition = .asymmetric(
insertion: .move(edge: .trailing),
removal: .move(edge: .leading)
)
let swapLeft: AnyTransition = .asymmetric(
insertion: .move(edge: .leading),
removal: .move(edge: .trailing)
)
var body: some View {
NavigationView{
VStack {
if currentMode == .calculate {
CalculateView(checkAmount: $checkAmount, numberOfPeople: $numberOfPeople, tipPercentage: $tipPercentage, currentMode: $currentMode, amountIsFocused: _amountIsFocused, tipPercentages: tipPercentages, currencyType: currencyType, grandTotal: grandTotal, totalPerPerson: totalPerPerson)
.transition(swapLeft)
} else {
HistoryView()
.transition(swapRight)
}
}
.animation(.easeInOut, value: currentMode)
.navigationTitle(navigationTitle)
.toolbar{
ToolbarItemGroup(placement: .keyboard) {
Button("Done"){
amountIsFocused = false
}
}
ToolbarItemGroup(placement: .principal) {
Picker("Mode", selection: $currentMode) {
ForEach(DisplayMode.allCases, id: \.self){ mode in
Text(mode.rawValue.capitalized)
}
}.pickerStyle(.segmented)
.fixedSize()
}
ToolbarItemGroup(placement: .navigationBarTrailing) {
if currentMode == .calculate {
Button {
print("Save")
} label: {
Text("Save")
}
}
}
}
}
}
}
Here's what it looks like on the simulator:
SwiftUI Form animation glitch
I can reproduce the issue, this seems to be a bug, the console is showing some constraint warnings.
A workaround could be using TabView. A positive side effect is that you won't need custom transitions, they come with it:
NavigationView{
TabView(selection: $currentMode) {
CalculateView(checkAmount: $checkAmount, numberOfPeople: $numberOfPeople, tipPercentage: $tipPercentage, currentMode: $currentMode, amountIsFocused: _amountIsFocused, tipPercentages: tipPercentages, currencyType: currencyType, grandTotal: grandTotal, totalPerPerson: totalPerPerson)
.tag(DisplayMode.calculate)
// .transition(swapLeft)
HistoryView()
.tag(DisplayMode.history)
// .transition(swapRight)
}
.tabViewStyle(.page(indexDisplayMode: .never) )
.animation(.easeInOut, value: currentMode)
// rest of your code as is
I have three Views exchanging information, ContentView, TimerView and TaskView. I used #Binding in TaskView to bring data from TaskView to ContentView, now I want to use that data to pass into TimerView.
I created a #State variable in ContentView to store the data from TaskView, but when I try to use that #State variable to pass data to TimerView it isn't giving me any desirable results.
Now if you see the TasksView call in ContentView, I get the output for the print statement, but in the main TimerView, it doesn't change from the default I set as #State variable in timerInfo.
Any help is appreciated, thank you.
Code for ContentView ->
//
// ContentView.swift
// ProDActivity
//
// Created by Vivek Pattanaik on 5/27/21.
//
import SwiftUI
struct TimerInfo : Identifiable {
let id = UUID()
// let taskIndex : Int
// let taskProjectName : String
var timerTaskName : String
var timerMinutes : Float
let timerIntervals : Int
var timerPriority : String
var timerShortbreakMinute : Float
var timerLongbreakMinute : Float
var timerLongbreakInterval : Int
}
struct ContentView: View {
init() {
UITabBar.appearance().backgroundColor = UIColor.init(Color("TabBar "))
}
// #State var selection: Tab = .dasboard
#State var timerInfo = TimerInfo(timerTaskName: "Sample Task 1", timerMinutes: 30, timerIntervals: 10, timerPriority: "High Priority", timerShortbreakMinute: 5, timerLongbreakMinute: 15, timerLongbreakInterval: 3)
var body: some View {
TabView {
TimerView(defaultTimeRemaining: self.timerInfo.timerMinutes * 60, timeRemaining: self.timerInfo.timerMinutes * 60)
.tabItem {
Image(systemName: "clock.fill")
Text("Timer")
}.tag(0)
TasksView(didClickTimer: { info in
self.timerInfo.timerTaskName = info.timerTaskName
self.timerInfo.timerMinutes = info.timerMinutes
self.timerInfo.timerPriority = info.timerPriority
self.timerInfo.timerShortbreakMinute = info.timerShortbreakMinute
self.timerInfo.timerLongbreakMinute = info.timerLongbreakMinute
self.timerInfo.timerLongbreakInterval = info.timerLongbreakInterval
print("\(self.timerInfo.timerMinutes)ContentView")
})
.tabItem {
Image(systemName: "doc.plaintext.fill")
Text("Tasks")
}.tag(1)
StatisticsView()
.tabItem {
Image(systemName: "chart.pie.fill")
Text("Statistics")
}.tag(3)
SettingsView()
.tabItem {
Image(systemName: "gearshape.fill")
Text("Settings")
}.tag(4)
}
.font(.headline)
.accentColor(Color("AccentColor"))
.environment(\.colorScheme, .dark)
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
Code for TasksView ->
//
// TasksView.swift
// ProDActivity
//
// Created by Vivek Pattanaik on 5/30/21.
//
import SwiftUI
struct TaskLabels : Identifiable {
let id = UUID()
// let taskIndex : Int
// let taskProjectName : String
let taskName : String
let taskPriority : String
let taskIntervals : String
let taskMinutes : String
let shortBreakMinutes : String
let longBreakMinutes : String
let longBreakIntervals : String
}
struct TaskRow: View {
let tasks : TaskLabels
var body: some View {
HStack(alignment: .center, spacing: 10) {
Image(tasks.taskPriority)
.frame(width: 40, height: 40, alignment: .center)
VStack(alignment: .leading, spacing:4){
Text(tasks.taskName)
.font(.system(size: 15))
Text("\(tasks.shortBreakMinutes) Min Breaks")
.font(.system(size: 13))
}
.frame(width: 100, height: 20, alignment: .leading)
Spacer()
VStack(alignment: .trailing, spacing:4){
Text("0/\(tasks.taskIntervals)")
Text("\(tasks.taskMinutes) Min Tasks")
.font(.system(size: 13))
}
.frame(width: 90, height: 20, alignment: .trailing)
.padding()
}
}
}
struct TasksView: View {
// #Binding var timeSelected : Float
#State var addTasksModalView: Bool = false
#State var taskLabels : [TaskLabels] = []
var didClickTimer : (TimerInfo) -> ()
var body: some View {
NavigationView{
if taskLabels.count != 0 {
List{
ForEach(taskLabels) { task in
HStack {
Button(action: {
self.didClickTimer(.init(timerTaskName: task.taskName, timerMinutes: Float(task.taskMinutes)!, timerIntervals: Int(task.taskIntervals)!, timerPriority: task.taskPriority, timerShortbreakMinute: Float(task.shortBreakMinutes)!, timerLongbreakMinute: Float(task.longBreakMinutes)!, timerLongbreakInterval: Int(task.longBreakIntervals)!))
print(task.taskMinutes)
}, label: {
TaskRow(tasks: task)
})
}
}
.onDelete(perform: self.deleteRow)
}
.navigationBarTitle("Tasks")
.navigationBarItems(trailing: Button(action: {
self.addTasksModalView = true
}, label: {
Image(systemName: "plus.square.on.square")
.resizable()
.frame(width: 26, height: 26, alignment: .leading)
.foregroundColor(Color.accentColor)
}))
.sheet(isPresented: $addTasksModalView, content: {
AddTasks(addTaskPresented: $addTasksModalView) { tasks in
taskLabels.append(tasks)
}
})
} else {
Text("")
.navigationTitle("Tasks")
.navigationBarTitle("Tasks")
.navigationBarItems(trailing: Button(action: {
self.addTasksModalView = true
}, label: {
Image(systemName: "plus.square.on.square")
.resizable()
.frame(width: 26, height: 26, alignment: .leading)
.foregroundColor(Color.accentColor)
}))
.sheet(isPresented: $addTasksModalView, content: {
AddTasks(addTaskPresented: $addTasksModalView) { tasks in
taskLabels.append(tasks)
}
})
}
}
.environment(\.colorScheme, .dark)
}
private func deleteRow(at indexSet: IndexSet){
self.taskLabels.remove(atOffsets: indexSet)
}
}
//struct TasksView_Previews: PreviewProvider {
// static var previews: some View {
// TasksView()
// }
//}
TimerView Code ->
//
// TimerView.swift
// ProDActivity
//
// Created by Vivek Pattanaik on 5/27/21.
//
import SwiftUI
struct TimerView: View {
let lineWidth : CGFloat = 11
let radius : CGFloat = 150
// to pass into the struct
// #State var taskTime = 300
#State var defaultTimeRemaining : Float
#State var timeRemaining : Float
#State private var isActive = false
#State private var showButtons = false
#State private var stopAlert = false
#State private var pausePressed = false
#State private var stopPressed = false
// #State private var timeRemainingSeconds : CGFloat = 25
let timer = Timer.publish(every: 1, on: .main, in: .common).autoconnect()
var body: some View {
// self.defaultTimeRemaining = timerInfo.timerMinutes
VStack(spacing : 60) {
ZStack{
RoundedRectangle(cornerRadius: 7)
.frame(width: 300, height: 70)
.foregroundColor(Color("TabBar "))
}
ZStack(alignment: Alignment(horizontal: .center, vertical: .center)) {
Circle()
.stroke(Color.gray, style: StrokeStyle(lineWidth: lineWidth, lineCap: .round))
.opacity(0.2)
Circle()
.trim(from: 1 - CGFloat(((defaultTimeRemaining-timeRemaining)/defaultTimeRemaining)), to: 1 )
.stroke(Color.accentColor, style: StrokeStyle(lineWidth: lineWidth, lineCap: .round))
.rotationEffect( .degrees(-90))
.animation(.easeInOut)
// VStack for the timer and seesions
VStack {
// Text("\(Int(timeRemaining)):\(Int(timeRemainingSeconds))")
Text("\(timeString(time: Int(timeRemaining)))")
.font(.system(size: 50)).fontWeight(.medium)
Text("0 of 5 Sessions")
.font(.system(size: 20)).fontWeight(.medium)
}
}.frame(width: radius*2, height: radius*2)
// BEGIN, STOP, PAUSE BUTTONS
HStack(spacing: 25){
if showButtons == false {
Button(action: {
}, label: {
ZStack {
Rectangle()
.frame(width: 176, height: 55, alignment: .center)
.foregroundColor(Color.accentColor)
.cornerRadius(5)
Button(action: {
self.showButtons.toggle()
isActive.toggle()
}, label: {
Text("BEGIN")
.foregroundColor(Color.white)
.font(.system(size: 23).weight(.medium))
.frame(width: 176, height: 55, alignment: .center)
})
}
})
} else if showButtons == true {
HStack {
ZStack {
Rectangle()
.frame(width: 152, height: 55, alignment: .center)
.foregroundColor(Color("LightDark"))
.cornerRadius(5)
.border(Color.accentColor, width: 2)
Button(action: {
self.stopPressed.toggle()
self.pausePressed = true
if isActive == true {
isActive.toggle()
self.stopAlert = true
} else {
self.stopAlert = true
}
}, label: {
Text("STOP")
.foregroundColor(Color.accentColor)
.font(.system(size: 23).weight(.medium))
.frame(width: 152, height: 55, alignment: .center)
})
.alert(isPresented: $stopAlert) {
Alert(title: Text("Are you sure you want to stop?"),
message: Text("This will stop the timer and task associated with it."),
primaryButton: .destructive(Text("Yes"), action: {
self.showButtons = false
timeRemaining = defaultTimeRemaining
}),
secondaryButton: .cancel({
isActive = false
pausePressed = true
})
)
}
}
ZStack {
Rectangle()
.frame(width: 152, height: 55, alignment: .center)
.foregroundColor(pausePressed ? Color.accentColor : Color("LightDark"))
.cornerRadius(5)
.border(pausePressed ? Color.accentColor : Color.accentColor, width: 2)
Button(action: {
pausePressed.toggle()
if pausePressed == true {
isActive = false
} else {
isActive = true
}
}, label: {
Text("\(pausePressed ? "RESUME" : "PAUSE")")
.foregroundColor(pausePressed ? Color("TabBar ") : Color.accentColor)
.font(.system(size: 23).weight(.medium))
.frame(width: 152, height: 55, alignment: .center)
})
}
}
}
}
}.onReceive(timer, perform: { _ in
guard isActive else {return}
if timeRemaining > 0 {
timeRemaining -= 1
} else {
isActive = false
showButtons = false
self.timer.upstream.connect().cancel()
}
})
}
func timeString(time: Int) -> String {
let minutes = Int(time) / 60 % 60
let seconds = Int(time) % 60
return String(format:"%02i:%02i", minutes, seconds)
}
}
//struct TimerView_Previews: PreviewProvider {
// static var previews: some View {
// TimerView()
// }
//}
//func buttonChange(){
//
//}
I'm trying to create my own PhoneTextField in and I have found a tutorial in here it's working fine if don't call it inside ZStack let's see the code example as below
countryCode View
struct CountryCodes: View {
#Binding var countryCode : String
#Binding var countryFlag : String
#Binding var y : CGFloat
var body: some View {
GeometryReader { geo in
List(self.countryDictionary.sorted(by: <), id: \.key) { key , value in
HStack {
Text("\(self.flag(country: key))")
Text("\(self.countryName(countryCode: key) ?? key)")
Spacer()
Text("+\(value)").foregroundColor(.secondary)
}.background(Color.white)
.font(.system(size: 20))
.onTapGesture {
self.countryCode = value
self.countryFlag = self.flag(country: key)
withAnimation(.spring()) {
self.y = 350
}
}
}
.padding(.bottom)
.frame(width: geo.size.width, height: 500)
.position(x: geo.frame(in: .global).midX, y: geo.frame(in: .global).maxY - -200)
}
}
let countryDictionary = ["AF":"93","AL":"355","DZ":"213","US":"1",
"AD":"376","AO":"244","AI":"1","AG":"1","AR":"54",
"AM":"374","AW":"297","AU":"61","AT":"43","AZ":"994",
"BS":"1","BH":"973","BD":"880","BB":"1","BY":"375",
"BE":"32","BZ":"501","BJ":"229","BM":"1","BT":"975",
"BA":"387","BW":"267","BR":"55","IO":"246","BG":"359",
"BF":"226","BI":"257","KH":"855","CM":"237","CA":"1",
"CV":"238","KY":"345","CF":"236","TD":"235","CL":"56","CN":"86",
"CX":"61","CO":"57","KM":"269","CG":"242","CK":"682","CR":"506",
"HR":"385","CU":"53","CY":"537","CZ":"420","DK":"45","DJ":"253",
"DM":"1","DO":"1","EC":"593","EG":"20","SV":"503","GQ":"240",
"ER":"291","EE":"372","ET":"251","FO":"298","FJ":"679","FI":"358",
"FR":"33","GF":"594","PF":"689","GA":"241","GM":"220","GE":"995",
"DE":"49","GH":"233","GI":"350","GR":"30","GL":"299","GD":"1",
"GP":"590","GU":"1","GT":"502","GN":"224","GW":"245","GY":"595","HT":"509",
"HN":"504","HU":"36","IS":"354","IN":"91","ID":"62","IQ":"964",
"IE":"353","IL":"972","IT":"39","JM":"1","JP":"81","JO":"962",
"KZ":"77","KE":"254","KI":"686","KW":"965","KG":"996","LV":"371",
"LB":"961","LS":"266","LR":"231","LI":"423","LT":"370","LU":"352",
"MG":"261","MW":"265","MY":"60","MV":"960","ML":"223",
"MT":"356","MH":"692","MQ":"596","MR":"222","MU":"230","YT":"262",
"MX":"52","MC":"377","MN":"976", "ME":"382","MS":"1","MA":"212",
"MM":"95","NA":"264","NR":"674","NP":"977","NL":"31","NC":"687",
"NZ":"64","NI":"505","NE":"227","NG":"234","NU":"683",
"NF":"672","MP":"1","NO":"47","OM":"968","PK":"92","PW":"680",
"PA":"507","PG":"675","PY":"595","PE":"51","PH":"63","PL":"48",
"PT":"351","PR":"1","QA":"974","RO":"40","RW":"250","WS":"685",
"SM":"378","SA":"966","SN":"221","RS":"381","SC":"248",
"SL":"232","SG":"65","SK":"421","SI":"386","SB":"677",
"ZA":"27","GS":"500","ES":"34","LK":"94","SD":"249","SR":"597",
"SZ":"268","SE":"46","CH":"41","TJ":"992","TH":"66","TG":"228",
"TK":"690","TO":"676","TT":"1","TN":"216","TR":"90",
"TM":"993","TC":"1","TV":"688","UG":"256","UA":"380",
"AE":"971","GB":"44","AS":"1","UY":"598","UZ":"998",
"VU":"678","WF":"681","YE":"967","ZM":"260",
"ZW":"263","BO":"591","BN":"673","CC":"61",
"CD":"243","CI":"225","FK":"500","GG":"44",
"VA":"379","HK":"852","IR":"98","IM":"44",
"JE":"44","KP":"850","KR":"82","LA":"856",
"LY":"218","MO":"853","MK":"389","FM":"691",
"MD":"373","MZ":"258","PS":"970","PN":"872",
"RE":"262","RU":"7","BL":"590","SH":"290","KN":"1",
"LC":"1","MF":"590","PM":"508","VC":"1","ST":"239",
"SO":"252","SJ":"47","SY":"963","TW":"886","TZ":"255",
"TL":"670","VE":"58","VN":"84","VG":"284","VI":"340"]
func countryName(countryCode: String) -> String? {
let current = Locale(identifier: "en_US")
return current.localizedString(forRegionCode: countryCode)
}
func flag(country:String) -> String {
let base : UInt32 = 127397
var flag = ""
for v in country.unicodeScalars {
flag.unicodeScalars.append(UnicodeScalar(base + v.value)!)
}
return flag
}
}
RegistrationView
//
// RegisterView.swift
// Instagram
//
// Created by Admin on 4/4/21.
//
import SwiftUI
import Combine
struct RegistrationView: View {
#State var isEditing: Bool = false
// navigate to verification view
#State private var selection: String? = nil
#State private var email:String = ""
#State private var phone:String = ""
#State private var fullname = ""
#State private var username = ""
#State private var password = ""
#State private var selectedImage:UIImage?
#State private var image:Image?
#Environment(\.presentationMode) var mode
#State var imagePickerPresented = false
#EnvironmentObject var viewModel: AuthViewModel
#State var isVerification = false
#State var phoneNumber = ""
#State var y : CGFloat = 350
#State var countryCode = ""
#State var countryFlag = ""
#State var isInputPhoneNumber = true
#State var isInputEmail = false
#State var tempData = DataTempo()
#State var isShowSignInButton = true
// var successClosure: (Bool) -> Void {
//
// }
#EnvironmentObject var mobileVerificationViewModel: MobileVerificationNvm
var body: some View {
ZStack {
LinearGradient(gradient: Gradient(colors: [Color.purple, Color("PrimaryColor")]), startPoint: .top, endPoint: .bottom)
.ignoresSafeArea()
VStack{
// Spacer()
VStack(spacing:20){
VStack(alignment:.leading){
Text("Sign Up")
.font(.largeTitle)
.bold()
.foregroundColor(.white)
}.padding(.horizontal,32).frame(width: UIScreen.main.bounds.width, alignment: .leading)
Toggle( isInputEmail ? "Use Email" : "Use Phone Number", isOn: $isInputEmail)
.foregroundColor(.white)
.toggleStyle(SwitchToggleStyle(tint: Color(#colorLiteral(red: 0.5568627715, green: 0.3529411852, blue: 0.9686274529, alpha: 1))))
.padding(.horizontal,32)
if isInputEmail == true {
CustomTextField(text: $email, placeholder: Text("Email"), imageName: "envelope")
.padding()
.background(Color(.init(white: 1, alpha: 0.15)))
.cornerRadius(10)
.foregroundColor(.white)
.padding(.horizontal,32)
.onChange(of: email) {
// self.autocomplete($0) // like this
if viewModel.isValidEmail(testStr: $0){
print("email")
//phone number
}
}
}
if isInputEmail == false{
ZStack(alignment:.center) {
HStack (spacing: 0) {
Text(countryCode.isEmpty ? "🇹🇠+66" : "\(countryFlag) +\(countryCode)")
.frame(width: 100, height: 50)
// .background(Color.secondary.opacity(0.2))
.background(Color(.init(white: 1, alpha: 0.15)))
.cornerRadius(10)
// .foregroundColor(countryCode.isEmpty ? .secondary : .black)
.foregroundColor(.white)
.onTapGesture {
isShowSignInButton = false
withAnimation (.spring()) {
self.y = 0
}
}
TextField("Phone Number", text: $phone)
.onChange(of: phone) { newValue in
if !phone.isEmpty {
self.isShowSignInButton = true
if viewModel.validatePhoneNumber(value:newValue){
print("phone")
//phone number
self.isInputPhoneNumber = true
self.isInputEmail = false
}
}
else{
self.isShowSignInButton = false
}
}
.frame(width: 250, height: 50)
.keyboardType(.phonePad)
.background(Color(.init(white: 1, alpha: 0.15)))
.foregroundColor(.white)
}
CountryCodes(countryCode: $countryCode, countryFlag: $countryFlag, y: $y)
.offset(y: y)
RoundedRectangle(cornerRadius: 10).stroke()
.frame(width: 350, height: 50)
.foregroundColor(.white)
} .zIndex(1).frame( height: 50, alignment: .center)
}
CustomTextField(text: $username, placeholder: Text("Username"), imageName: "person")
.padding()
.background(Color(.init(white: 1, alpha: 0.15)))
.cornerRadius(10)
.foregroundColor(.white)
.padding(.horizontal,32)
CustomTextField(text: $fullname, placeholder: Text("Full Name"), imageName: "person")
.padding()
.background(Color(.init(white: 1, alpha: 0.15)))
.cornerRadius(10)
.foregroundColor(.white)
.padding(.horizontal,32)
CustomSecureField(text: $password, placeholder: Text("Password"))
.padding()
.background(Color(.init(white: 1, alpha: 0.15)))
.cornerRadius(10)
.foregroundColor(.white)
.padding(.horizontal,32)
}
.alert(isPresented: $isInputPhoneNumber, content: {
Alert(title: Text("Message"), message: Text("you're using phone number to register"), dismissButton: .default(Text("Bye alert!"), action: {
isInputPhoneNumber = true
}))
})
.alert(isPresented: mobileVerificationViewModel.alertBinding) {
Alert(title: Text(mobileVerificationViewModel.errorMessage ?? "(unknown)"), message: nil, dismissButton: .cancel())
}
HStack{
Spacer()
Button(action: /*#START_MENU_TOKEN#*/{}/*#END_MENU_TOKEN#*/, label: {
Text("Forgot Password")
})
.font(.system(size: 13, weight: .semibold))
.foregroundColor(.white)
.padding(.top)
.padding(.trailing,28)
}
ZStack{
NavigationLink(destination:VerificationView { (pin, successClosure) in
print(pin)
mobileVerificationViewModel.codeVerification(phoneNumber: isInputEmail ? tempData.email : tempData.phone, pinCode: pin, email: email, password: password, fullname: fullname, username: username)
//navigation back
self.mode.wrappedValue.dismiss()
// print(successClosure)
}, tag: "verification", selection: $selection) { EmptyView() }
Button(action: {
self.selection = "verification"
var phoneWithCountryCode = ""
// if countryCode == "" {
// phoneWithCountryCode = "+66\(phone)"
// }
// if countryCode != ""{
// phoneWithCountryCode = "+\(countryCode)\(phone)"
// }
//
// // "+\(countryCode == "" ? "66" : countryCode + phone)"
// if (!email.isEmpty || !phone.isEmpty) && !password.isEmpty{
//
// if isInputEmail {
// MobileVerificationNvm.shared.sendVerification(phoneNumber:email)
// isVerification = true
// tempData.email = email
// }
//
// else{
// DispatchQueue.main.async {
// // print("phone:\(phoneWithCountryCode)")
// MobileVerificationNvm.shared.sendVerification(phoneNumber: phoneWithCountryCode)
// isVerification = true
// tempData.phone = phone
// }
//
// }
//
// }
// viewModel.register(withEmail: email, password: password,image: selectedImage,fullname: fullname,username: username)
}, label: {
Text("Sign Up")
}).zIndex(0)
// Spacer()
}
.font(.headline)
.frame(width: 360, height: 50)
.foregroundColor(.white)
.background(Color(#colorLiteral(red: 0.5568627715, green: 0.3529411852, blue: 0.9686274529, alpha: 1)))
// .clipShape(Capsule())
.clipShape(RoundedRectangle(cornerRadius: 16, style: .continuous))
.padding()
.alert(isPresented: $isInputEmail, content: {
Alert(title: Text("Message"), message: Text("Now! you're using Email to register but you also register with email just swtich on togle button"), dismissButton: .default(Text("Bye alert!"), action: {
isInputEmail = true
}))
})
Button(action: {
mode.wrappedValue.dismiss()
// isVerification = true
}, label: {
Text("Already have an account? ")
.font(.system(size: 14))
+ Text("Sign In")
.font(.system(size: 14, weight: .bold))
})
.foregroundColor(.white)
.padding(.bottom,32)
}
}
.ignoresSafeArea()
}
}
extension RegistrationView{
func loadImage() {
guard let selectedImage = selectedImage else {
return
}
image = Image(uiImage: selectedImage)
}
}
//
struct RegistrationView_Previews: PreviewProvider {
static var previews: some View {
RegistrationView()
}
}
this is my issue when I tap on country code to choose the country code you will see the button sign in over countries code list
you also can check from [here]:github.com/PhanithNoch/SwiftUIPhoneTextField this is the repo that I tried to separate code from my real project as an example
I stuck with this a few days ago hope anyone can help, please.
I have an array of views that I need to get values from in order to save them to my context. When trying to loop over these when a button is clicked I get a "Type '()' cannot conform to 'View'; only struct/enum/class types can conform to protocols" exception. Im very new to swift and I am wondering if there is an easier way to do this. Thank you in advanced!
This is the complete view:
struct DetailedView: View {
#Environment(\.managedObjectContext) var moc
var targetMuscle : String = "Chest"
let today = Date()
#State public var exerciseCards : [ExerciseCard] = []
#State public var exercise : String = "Bench Press"
#State public var exercises : Int = 0
#State public var showPassedWorkouts : Bool = false
static let taskDateFormat : DateFormatter = {
let formatter = DateFormatter()
formatter.dateStyle = .long
return formatter
}()
var body: some View {
ZStack{
VStack{
HStack{
VStack{
Text(targetMuscle).font(.system(size:40)).fontWeight(.medium)
Text("\(today, formatter: Self.taskDateFormat)")
.font(.system(size:20))
}.frame(width: 250, height: 30, alignment: .topLeading)
.navigationBarTitle("")
.navigationBarHidden(true)
.padding(.bottom, -7)
Button(action: {
self.showPassedWorkouts.toggle()
}) {
Text("Passed Workouts")
.multilineTextAlignment(.center)
}.offset(x: -75, y: 25)
.sheet(isPresented: $showPassedWorkouts){
PassedWorkoutList()
}
Button(action: {
let workout = Workout(context: self.moc)
workout.muscle = self.targetMuscle
workout.date = formattedDateString(day: self.today)
ForEach(0..<exerciseCards.count, id: \.self){number in
let exercise = Exercise(context: self.moc)
ForEach(0..<self.exerciseCards[number].tableRows.count, id: \.self){innerNum in
let exerciseSet = ExerciseSet(context: self.moc)
exerciseSet.reps = self.exerciseCards[number].tableRows[innerNum].reps
exerciseSet.weight = self.exerciseCards[number].tableRows[innerNum].weight
exerciseSet.set = self.exerciseCards[number].tableRows[innerNum].set
exercise.addToExerciseSet(exerciseSet)
}
workout.addToExercise(exercise)
}
try? self.moc.save()
}) {
Text("Finish")
}.offset(x: -20, y: 20)
}.padding(.bottom, 35)
.padding(.leading)
ScrollView{
ForEach(0..<exerciseCards.count, id: \.self){ number in
self.exerciseCards[number]
}
Button(action: {
self.exerciseCards.append(ExerciseCard())
}) {
Text("Add Exercise")
.frame(minWidth: 325)
.padding()
.foregroundColor(.white)
.background(Color.blue.opacity(0.7))
.cornerRadius(20)
}.padding(.top)
.frame(width: 400)
}
}
}.background(Color.white)
}
}
func formattedDateString(format: String? = "MMM d, h:mm", day: Date) -> String {
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = format
return dateFormatter.string(from: day)
}
The above view also references two views the first one being the ExerciseCard view
struct ExerciseCard: View {
#State public var exercise : String = "Bench Press"
#State public var tableRows : [ExerciseTableRow] = []
var body: some View {
VStack{
TextField("Enter Exercise", text: $exercise).textFieldStyle(RoundedBorderTextFieldStyle())
.frame(width: 300)
.multilineTextAlignment(.center)
HStack{
Group{
Text("Set")
Text("Weight")
Text("Reps")
}.padding(.horizontal, 30)
.offset(x: -20, y: 0)
}
VStack{
ForEach(0..<tableRows.count, id: \.self){ number in
self.tableRows[number]
}
}.padding(.bottom, 5)
HStack{
Button(action: {
if self.tableRows.count > 1{
self.tableRows.remove(at: self.tableRows.count-1)
}
}) {
Text("Remove Set")
.frame(minWidth: 150)
.padding(.vertical, 5)
.foregroundColor(.white)
.background(Color.red)
.cornerRadius(20)
}
Button(action: {
self.tableRows.append(ExerciseTableRow(set: 2, readOnly: false, setWeight: 2, setReps: 2))
}) {
Text("Add Set")
.frame(minWidth: 150)
.padding(.vertical, 5)
.foregroundColor(.white)
.background(Color.green)
.cornerRadius(20)
}
}
}
.padding()
.padding(.vertical)
.background(Color.offWhite)
.cornerRadius(20)
.shadow(color: Color.black.opacity(0.2), radius: 10, x:10, y:10)
.shadow(color: Color.white.opacity(0.7), radius: 10, x:-5, y:-5)
}
}
The second one being the ExerciseTableRow view
struct ExerciseTableRow: View {
#State public var weight : String = "0"
#State public var reps : String = "0"
var set : Int16
var readOnly : Bool
var setWeight : Int16
var setReps : Int16
var body: some View {
HStack{
Text(String(set))
.padding(.trailing, 40)
.padding(.leading, 10)
if readOnly == false{
Group{
TextField("0", text: $weight)
TextField("0", text: $reps)
}.textFieldStyle(RoundedBorderTextFieldStyle())
.frame(width: 50)
.multilineTextAlignment(.center)
.keyboardType(.numberPad)
.padding(.horizontal, 30)
}
else{
Group{
Text(String(setWeight))
Text(String(setReps))
}
.frame(width: 50)
.overlay(
RoundedRectangle(cornerRadius: 5)
.stroke(Color.black, lineWidth: 1)
)
.padding(.bottom, 5)
.padding(.horizontal, 30)
}
}
}
}
The ForEach is a SwiftUI dynamic View container, to build views in cycle. For action you have to use regular swift for .. in expression, like
Button(action: {
let workout = Workout(context: self.moc)
workout.muscle = self.targetMuscle
workout.date = formattedDateString(day: self.today)
for number in 0..<exerciseCards.count {
let exercise = Exercise(context: self.moc)
for innerNum in 0..<self.exerciseCards[number].tableRows.count {
let exerciseSet = ExerciseSet(context: self.moc)
exerciseSet.reps = self.exerciseCards[number].tableRows[innerNum].reps
exerciseSet.weight = self.exerciseCards[number].tableRows[innerNum].weight
exerciseSet.set = self.exerciseCards[number].tableRows[innerNum].set
exercise.addToExerciseSet(exerciseSet)
}
workout.addToExercise(exercise)
}
try? self.moc.save()
}) {
Text("Finish")
}.offset(x: -20, y: 20)