I currently have the code below:
var arrayEX = ["8:00 - 11:00", "11:00 - 2:00", "2:00 - 5:00"]
#State var selectedtime = ""
Picker("Start time", selection: $selectedtime) {
ForEach(arrayEX, id: \.self) {
Text($0)
}
}
.padding()
.pickerStyle(SegmentedPickerStyle())
My question is; How can I not show the 1st element of the array after 8:00am, the 2nd element after 11:00am, and the 3rd element after 2:00pm with a message saying there are no times left today?
you could try this approach, where the start times are converted to minutes and then filtered according to the current time.
struct ContentView: View {
// -- here, note 24h clock and consistent pattern `hh:mm`
var arrayEX = ["08:00 - 11:00", "11:00 - 14:00", "14:00 - 17:00"]
#State var selectedtime = ""
#State var timesLeft: [String] = [] // <-- here
var body: some View {
Picker("Start time", selection: $selectedtime) {
ForEach(timesLeft, id: \.self) { // <-- here timesLeft
Text($0)
}
}
.padding()
.pickerStyle(SegmentedPickerStyle())
.onAppear {
let hour = Calendar.current.component(.hour, from: .now)
let minute = Calendar.current.component(.minute, from: .now)
let minutesNow = (Int(hour)*60) + Int(minute) // <-- minutes since the start of day
// convert start times to minutes
let startTimes: [Int] = arrayEX.map{
let h = String($0.prefix(2))
let m = String($0.dropFirst(3).prefix(2))
return ((Int(h) ?? 0) * 60) + (Int(m) ?? 0)
}
// find the index in arrayEX where we are past the time
if let ndx = startTimes.firstIndex(where: { $0 > minutesNow }) {
timesLeft = Array(arrayEX.suffix(from: ndx))
} else {
timesLeft = ["no times left today"]
}
}
}
}
Related
Although I get an animation when I tap the button, it's not the animation I want.
The entire view is being replaced at once, but I want to see each element change in sequence. I tried in both the parent view and in the called method. Neither produces the desired result.
(this is a simplified version of the original code)
import SwiftUI
struct SequencedCell: Identifiable {
let id = UUID()
var value: Int
mutating func addOne() {
value += 1
}
}
struct AQTwo: View {
#State var cells: [SequencedCell]
init() {
_cells = State(initialValue: (0 ..< 12).map { SequencedCell(value: $0) })
}
var body: some View {
VStack {
Spacer()
Button("+") {
sequencingMethod(items: $cells)
}
.font(.largeTitle)
Spacer()
HStack {
ForEach(Array(cells.enumerated()), id: \.1.id) { index, item in
// withAnimation(.linear(duration: 4)) {
Text("\(item.value)").tag(index)
// }
}
}
Spacer()
}
}
func sequencingMethod(items: Binding<[SequencedCell]>) {
for cell in items {
withAnimation(.linear(duration: 4)) {
cell.wrappedValue = SequencedCell(value: cell.wrappedValue.value + 1)
// cell.wrappedValue.addOne()
}
}
}
}
struct AQTwoPreview: PreviewProvider {
static var previews: some View {
AQTwo()
}
}
So I want the 0 to turn into a 1, the 1 then turn into a 2, etc.
Edit:
Even though I have accepted an answer, it answered my question, but didn't solve my issue.
I can't use DispatchQueue.main.asyncAfter because the value I am updating is an inout parameter and it makes the compiler unhappy:
Escaping closure captures 'inout' parameter 'grid'
So I tried Malcolm's (malhal) suggestion to use delay, but everything happens immediately with no sequential animation (the entire block of updated items animate as one)
Here's the recursive method I am calling:
static func recursiveAlgorithm(targetFill fillValue: Int, in grid: inout [[CellItem]],
at point: (x: Int, y: Int), originalFill: Int? = nil, delay: TimeInterval) -> [[CellItem]] {
/// make sure the point is on the board (or return)
guard isValidPlacement(point) else { return grid }
/// the first time this is called we don't have `originalFill`
/// so we read it from the starting point
let tick = delay + 0.2
//AnimationTimer.shared.tick()
let startValue = originalFill ?? grid[point.x][point.y].value
if grid[point.x][point.y].value == startValue {
withAnimation(.linear(duration: 0.1).delay(tick)) {
grid[point.x][point.y].value = fillValue
}
_ = recursiveAlgorithm(targetFill: fillValue, in: &grid, at: (point.x, point.y - 1), originalFill: startValue, delay: tick)
_ = recursiveAlgorithm(targetFill: fillValue, in: &grid, at: (point.x, point.y + 1), originalFill: startValue, delay: tick)
_ = recursiveAlgorithm(targetFill: fillValue, in: &grid, at: (point.x - 1, point.y), originalFill: startValue, delay: tick)
_ = recursiveAlgorithm(targetFill: fillValue, in: &grid, at: (point.x + 1, point.y), originalFill: startValue, delay: tick)
}
return grid
}
Further comments/suggestions are welcome, as I continue to wrestle with this.
As mentioned in the comments, the lowest-tech version is probably just using a DisatpchQueue.main.asyncAfter call:
func sequencingMethod(items: Binding<[SequencedCell]>) {
var wait: TimeInterval = 0.0
for cell in items {
DispatchQueue.main.asyncAfter(deadline: .now() + wait) {
withAnimation(.linear(duration: 1)) {
cell.wrappedValue = SequencedCell(value: cell.wrappedValue.value + 1)
}
}
wait += 1.0
}
}
You could use delay(_:) for that, e.g.
func sequencingMethod(items: Binding<[SequencedCell]>) {
var delayDuration = 0.0
for cell in items {
withAnimation(.linear(duration: 4).delay(delayDuration)) {
cell.wrappedValue = SequencedCell(value: cell.wrappedValue.value + 1)
}
delayDuration += 0.5
}
}
I am brand new to Swift (and coding in general). I am working on an app that will output a calculation based off the tracking of two states. The two states are brewModel and waterAmount. I am able to successfully create a function that will return one calculation based on the two states. However, now I am trying to create a Picker that will toggle the calculation between two measurements - grams and tablespoons. This is where I am having trouble.
I tried to write a series of conditionals in different ways such as if and else if as well as switch cases, but it doesn't work. When I build the simulator, Xcode will just think for a long time until I stop it. Sometimes I get error messages after I manually stop it and sometimes I don't. Today I got "Command CompileSwiftSources failed with a nonzero exit code."
Does anyone have any suggestions?
Also, I apologize if my code is messy, I have a bunch of things commented out that I am playing with. The func computeGrinds does work but just for the one calculation. Thank you!
import SwiftUI
struct Water: View {
// #EnvironmentObject var favorites: Favorites
#State var animationInProgress = true
#State var brewModel: BrewModel
#State var waterAmount: Int = 1
#State var grindsSelection = "tbsp"
var grindOptions = ["tbsp", "grams"]
// var resultGrindCalc: Double {
//
// var value = Double(0)
// }
// switch grindsSelection {
// case "tbsp" || brewModel.frenchPress:
// value = Double(waterAmount) * 2.5
//
// }
//
// func computeGrinds () -> Double {
// switch brewModel {
// case .frenchPress, .chemex:
// return (2.5 * Double(waterAmount))
// case .drip :
// return Double(2 * Double(waterAmount))
// case .mokaPot:
// return Double(1 * Double(waterAmount))
// case .aeroPress:
// return Double(1.6 * Double(waterAmount))
// // default:
// // return(1 * Double(waterAmount))
// }
// }
var body: some View {
VStack (spacing: 5) {
Spacer()
HStack {
// Text("").padding(20)
Text("How many cups do you want to brew?")
Picker("", selection: $waterAmount) {
ForEach(1...15, id: \.self){
Text("\($0)")
}
}
// Spacer()
}.padding()
.overlay (
RoundedRectangle(cornerRadius: 16)
.stroke(Color("Custom Color"), lineWidth: 8)
)
// gif/image conditionals
if (brewModel == .frenchPress) {
LottieView(name: "frenchpress", loopMode: .loop)
} else if brewModel == .chemex {
LottieView(name: "pourover", loopMode: .loop)
} else if brewModel == .aeroPress {
LottieView(name: "aeropress", loopMode: .loop)
} else if brewModel == .mokaPot {
LottieView(name: "mokapot", loopMode: .loop)
} else if brewModel == .drip {
Image("Drip")
.resizable()
.scaledToFit()
}
// I would have more conditionals but testing with just these two for now
var testingCalcCond = Double
if (brewModel == .frenchPress)||(grindsSelection=="tbsp") {
testingCalcCond = (2.5 * Double(waterAmount))
} else if (brewModel == .frenchPress)||(grindsSelection=="grams") {
testingCalcCond = (16 * Double(waterAmount))
}
let formatted = String(format: "%.2f", testingCalcCond)
// let formatted = String(format: "%.2f", computeGrinds())
HStack {
Text("**\(formatted)**")
Picker("Select Grinds Units: ", selection: $grindsSelection, content: {
ForEach(grindOptions, id: \.self) {
Text($0)
}
}).onChange(of: grindsSelection) { _ in computeGrinds() }
Text("of coffee grinds needed")
}
.padding()
.overlay (
RoundedRectangle(cornerRadius: 16)
.stroke(Color("Custom Color"), lineWidth: 8)
)
}
Spacer()
}
}
struct Water_Previews: PreviewProvider {
static var previews: some View {
Water(brewModel: .drip)
}
}
}
*I'm using Xcode 13.2.1
*I'm using swiftUI
There are 2 aspects you want to think over:
1. How to update the result value based on the inputs.
Your result value is based on two inputs: brewModel and waterAmount. Both are #State vars and changed by a picker.
I changed your computeGrinds func to a computed property, because this will be automatically called when one of the two base values changes. Then there is no need for .onchange anymore, you can just use the var value – it will always be up to date.
2. recalculating from tbsp to grams.
This is more of a math thing: As I understand, for .frenchPress you need either 2.5 tbsp – or 16 grams per cup. So 1 tbsp = 16 / 2.5 = 6.4 grams. Once you know that you just have to go through the switch case once, and use the unitValue to recalculate. I integrated that too ;)
Here is my simplified code:
enum BrewModel {
case frenchPress
case chemex
case drip
case mokaPot
case aeroPress
}
struct ContentView: View {
#State var animationInProgress = true
#State var brewModel: BrewModel = .frenchPress
#State var waterAmount: Int = 1
#State var grindsSelection = "tbsp"
let grindOptions = ["tbsp", "grams"]
// computed var instead of func, does the same
var computeGrinds: Double {
// transforms tbsp = 1 to grams (= 6.4 ?)
var unitValue: Double = 1.0
if grindsSelection == "grams" {
unitValue = 6.4
}
switch brewModel {
case .frenchPress, .chemex:
return (2.5 * unitValue * Double(waterAmount))
case .drip :
return Double(2 * unitValue * Double(waterAmount))
case .mokaPot:
return Double(1 * unitValue * Double(waterAmount))
case .aeroPress:
return Double(1.6 * unitValue * Double(waterAmount))
}
}
var body: some View {
VStack (spacing: 5) {
HStack {
Text("How many cups do you want to brew?")
Picker("", selection: $waterAmount) {
ForEach(1...15, id: \.self){
Text("\($0)")
}
}
}
.padding()
.overlay (
RoundedRectangle(cornerRadius: 16)
.stroke(Color.brown, lineWidth: 8)
)
.padding(.bottom)
let formatted = String(format: "%.2f", computeGrinds)
HStack {
Text("**\(formatted)**")
Picker("Select Grinds Units: ", selection: $grindsSelection, content: {
ForEach(grindOptions, id: \.self) {
Text($0)
}
})
Text("of coffee grinds needed")
}
.padding()
.overlay (
RoundedRectangle(cornerRadius: 16)
.stroke(Color.brown, lineWidth: 8)
)
}
}
}
I'm trying to get a dancing letters effect when my app first opens.
I'm close. The coding below almost does what I want. I use a ForEach loop to loop through the letters of the word and apply an animation to each letter. And I use the onAppear function to set the drag amount when the app opens.
With this coding I can get the 'forward' motion but I can't get the animation to reverse so that the letters end up in their original position. I've tried adding a repeat with reverse, but, again, the letters never return to their original position
Does anyone have any idea how to do this?
struct ContentView: View {
let letters = Array("Math Fun!")
#State private var enabled = false
#State private var dragAmount = CGSize.zero
var body: some View {
HStack(spacing: 0) {
ForEach(0..<letters.count) { num in
Text(String(self.letters[num]))
.padding(5)
.font(.title)
.background(self.enabled ? Color.blue : Color.red)
.offset(self.dragAmount)
.animation(Animation.default.delay(Double(num)/20).repeatCount(3, autoreverses: true))
}
}
.onAppear {
self.dragAmount = CGSize(width: 0, height: 80)
self.enabled.toggle()
}
}
}
Update: with Xcode 13.4 / iOS 15.5
Animation is based on changed states, we switched states and view animated to the new states, so to rollback we need to switch the states back.
Here is the possible approach (might still require tuning, but is ok for demo)
struct ContentView: View {
let letters = Array("Math Fun!")
#State private var enabled = false
#State private var dragAmount = CGSize.zero
var body: some View {
HStack(spacing: 0) {
ForEach(0..<letters.count, id: \.self) { num in
Text(String(self.letters[num]))
.padding(5)
.font(.title)
.background(self.enabled ? Color.blue : Color.red)
.offset(self.dragAmount)
.animation(Animation.default.delay(Double(num)/20), value: enabled)
}
}
.onAppear {
self.dragAmount = CGSize(width: 0, height: 80)
self.enabled.toggle()
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
self.dragAmount = .zero
self.enabled.toggle()
}
}
}
}
You can use AnimatableModifier to achieve this effect. here is a sample code:
extension Double {
var rad: Double { return self * .pi / 180 }
var deg: Double { return self * 180 / .pi }
}
struct ContentView: View {
#State private var flag = false
var body: some View {
VStack {
Spacer()
Color.clear.overlay(WaveText("Your Text That Need Animate", waveWidth: 6, pct: flag ? 1.0 : 0.0).foregroundColor(.blue)).frame(height: 40)
Spacer()
}.onAppear {
withAnimation(Animation.easeInOut(duration: 2.0).repeatForever()) {
self.flag.toggle()
}
}
}
}
struct WaveText: View {
let text: String
let pct: Double
let waveWidth: Int
var size: CGFloat
init(_ text: String, waveWidth: Int, pct: Double, size: CGFloat = 34) {
self.text = text
self.waveWidth = waveWidth
self.pct = pct
self.size = size
}
var body: some View {
Text(text).foregroundColor(Color.clear).modifier(WaveTextModifier(text: text, waveWidth: waveWidth, pct: pct, size: size))
}
struct WaveTextModifier: AnimatableModifier {
let text: String
let waveWidth: Int
var pct: Double
var size: CGFloat
var animatableData: Double {
get { pct }
set { pct = newValue }
}
func body(content: Content) -> some View {
HStack(spacing: 0) {
ForEach(Array(text.enumerated()), id: \.0) { (n, ch) in
Text(String(ch))
.scaleEffect(self.effect(self.pct, n, self.text.count, Double(self.waveWidth)))
}
}
}
func effect(_ pct: Double, _ n: Int, _ total: Int, _ waveWidth: Double) -> CGFloat {
let n = Double(n)
let total = Double(total)
return CGFloat(1 + valueInCurve(pct: pct, total: total, x: n/total, waveWidth: waveWidth))
}
func valueInCurve(pct: Double, total: Double, x: Double, waveWidth: Double) -> Double {
let chunk = waveWidth / total
let m = 1 / chunk
let offset = (chunk - (1 / total)) * pct
let lowerLimit = (pct - chunk) + offset
let upperLimit = (pct) + offset
guard x >= lowerLimit && x < upperLimit else { return 0 }
let angle = ((x - pct - offset) * m)*360-90
return (sin(angle.rad) + 1) / 2
}
}
}
You can find refrence and compelete answer here
I'm trying to do a fairly simple View with MVVM to be a good ViewModel citizen. However,
the code breaks while accessing the #Enviromnent Core Data in the ViewModel. I created
two functions in the ViewModel. One accesses Core Data through the #Environment and one
accesses Core Data with the old style - get a reference to AppDelegate and do my own
thing. The OldSchool method works. Comment 2 below. The #Environment does not - it breaks
at the line indicated below with an error that is not helpful for me. Comment 1.
(Thread 1: EXC_BAD_INSTRUCTION (code=EXC_I386_INVOP, subcode=0x0))
Now if I take the same #Environment code and put it directly into the view it Works.
And if I call the same line that breaks the MVVM in a Text in the View I get the
correct response. Comment 2
This is the view:
struct UserUtilities: View {
#Environment(\.managedObjectContext) var managedObjectContext
#FetchRequest(fetchRequest: DermPhoto.getAllDermPhotos()) var dermPhotos: FetchedResults<DermPhoto>
#State private var reminderInterval = 1
#State private var enableReminders = true
#State private var daysSincePhoto: Int = 0
#ObservedObject var userUtilitiesVM = UserUtilitiesViewModel()
var body: some View {
NavigationView {
VStack {
Group { //group 1
Toggle(isOn: $enableReminders) {
Text("Enable Reminders")
}
.padding(EdgeInsets(top: 50, leading: 50, bottom: 0, trailing: 50))
Text("Reminders are" + (enableReminders == true ? " On" : " Off"))
Spacer()
//Comment 3 - this always works
Text("String interpolation of self.dermPhotos.count")
Text("\(self.dermPhotos.count)")
} //group 1
Group { //group 2
Text("It has been " + "\(self.daysSincePhoto) " + (daysSincePhoto == 1 ? "day" : "days") + " since a photo was added.")
.padding(EdgeInsets(top: 0, leading: 50, bottom: 0, trailing: 50))
//options for the sentence above
//\(self.userUtilitiesVM.getTheDateInterval())
//\(self.userUtilitiesVM.getFromEnvironment())
//\(self.userUtilitiesVM.dateInterval)
Spacer()
Stepper("Reminder Interval", value: $reminderInterval, in: 1 ... 30)
.padding(EdgeInsets(top: 0, leading: 50, bottom: 0, trailing: 50))
Text("Reminder Interval is: \(reminderInterval)" + (reminderInterval == 1 ? " day" : " days"))
Spacer()
}//group 2
}
.navigationBarTitle("Reminder Days", displayMode: .inline)
.navigationBarItems(trailing: Button(action: {
print("getting the date interval from the nav button")
//self.daysSincePhoto = self.userUtilitiesVM.getOldSchool()
self.daysSincePhoto = self.userUtilitiesVM.getFromEnvironment()
//self.daysSincePhoto = self.getFromEnvironment()
} , label: { Text("Fetch")
}))
}
}
//this always works
func getFromEnvironment() -> Int {
let numberOfRecords = self.dermPhotos.count
if numberOfRecords > 0 {
let now = Date()
let lastDate = self.dermPhotos.last?.addDate
//ok to bang - addDate is always added to core data
let dateInterval = DateInterval(start: lastDate!, end: now)
let days = Int(dateInterval.duration) / (24 * 3600)
self.daysSincePhoto = days
return days
}
return 0
}
}
And this is the ViewModel:
class UserUtilitiesViewModel: ObservableObject {
#Environment(\.managedObjectContext) var managedObjectContext
#FetchRequest(fetchRequest: DermPhoto.getAllDermPhotos()) var dermPhotos: FetchedResults<DermPhoto>
#Published var dateInterval: Int = 50
//Comment 2 this always works
func getOldSchool() -> Int {
let kAppDelegate = UIApplication.shared.delegate as! AppDelegate
let context = kAppDelegate.persistentContainer.viewContext
var resultsDermPhotos : [DermPhoto] = []
let fetchRequest: NSFetchRequest<DermPhoto> = DermPhoto.fetchRequest() as! NSFetchRequest<DermPhoto>
let sortDescriptor = NSSortDescriptor(key: "addDate", ascending: true)
fetchRequest.sortDescriptors = [sortDescriptor]
do {
resultsDermPhotos = try context.fetch(fetchRequest)
} catch {
print("the fetchRequest error is \(error.localizedDescription)")
}
let numberOfRecords = resultsDermPhotos.count
if numberOfRecords > 0 {
let now = Date()
let lastDate = resultsDermPhotos.last?.addDate
//ok to bang this since addDate is always added to core data
let di = DateInterval(start: lastDate!, end: now)
let days = Int(di.duration) / (24 * 3600)
dateInterval = days
return days
}
return 0
}
//this never works
func getFromEnvironment() -> Int {
//Comment 1 - this breaks
let numberOfRecords = self.dermPhotos.count
if numberOfRecords > 0 {
let now = Date()
let lastDate = self.dermPhotos.last?.addDate
//ok to bang this since addDate is always added to core data
let di = DateInterval(start: lastDate!, end: now)
let days = Int(di.duration) / (24 * 3600)
dateInterval = days
return days
}
return 0
}
}
Clearly I can use old school or abandon the ViewModel idea but I would like to know how to fix this. Any guidance would be appreciated. Xcode 11.3 (11C29)
I don't think you can access #Environment from outside View hierarchy.
Your view model does not conform to view. But it won't be view model once it conforms to it.
=> you should abandon view model idea
I'm receiving the following error when I try and build an Xcode project using SwiftUI.
The error I get is "The compiler is unable to type-check this expression in reasonable time; try breaking up the expression into distinct sub-expressions"
I have tried to remove a lot of the complexities of the code to simplify it down as much as possible. I also tried reducing the number of lines of Picker's to test whether that was the issues.
struct ContentView: View {
// set child name
#State private var childName: String = ""
// set the days of the week
let daysOfWeek = ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday"]
// set empty
#State private var selectedAttendance: String = ""
let attendance = ["None", "All Day", "AM", "PM"]
#State private var mondayAttendance = 0
#State private var tuesdayAttendance = 0
#State private var wednesdayAttendance = 0
#State private var thursdayAttendance = 0
#State private var fridayAttendance = 0
#State private var selectedProvider = 0
let providers = ["School 1", "School 2", "School 3", "School 4", "School 5"]
var body: some View {
NavigationView {
Form {
Section(header: Text("Your Child's Details")) {
TextField("Name", text: $childName)
}
// PICKER to choose childcare provider
Section(header: Text("Who looks after your child?")) {
Picker("Childcare Provider", selection: $selectedProvider) {
ForEach(0 ..< providers.count) {
Text("\(self.providers[$0])")
}
}
}
Section(header: Text("Which days does your child attend?")) {
VStack {
// Monday
HStack {
Text("Monday")
.dayOfWeek()
Picker("Monday", selection: $mondayAttendance) {
ForEach(0 ..< 4) {
Text("\(self.attendance[$0])")
}
}
.pickerStyle(SegmentedPickerStyle())
}
// Tuesday
HStack {
Text("Tuesday")
.dayOfWeek()
Picker("Tuesday", selection: $tuesdayAttendance) {
ForEach(0 ..< 4) {
Text("\(self.attendance[$0])")
}
}
.pickerStyle(SegmentedPickerStyle())
}
//
// // Wednesday
// HStack {
// Text("Wednesday")
// .dayOfWeek()
// Picker("Wednesday", selection: $wednesdayAttendance) {
// ForEach(0 ..< 4) {
// Text("\(self.attendance[$0])")
// }
// }
// .pickerStyle(SegmentedPickerStyle())
// }
}
}
}
.navigationBarTitle("Your Child")
}
}
}
When I try and build with just "Monday" and "Tuesday" showing, it works. As soon as I uncomment Wednesday (or more), the build fails and I get the error.
I understand that I need to simplify the code, but it's pretty simple already and I'm not sure where to take it next.
Any help is much appreciated.
It has nothing to do with For Each. Where is the definition of dayOfWeek() that is attached to Text. It should return a view. It should have failed even with one For Each as it had dayOfWeek(). Probably that modifier might have been defined in your app.