DatePicker, Having A Range or Selecting Date Causes View To Shift - swift

I have an odd issue where my view is being shifted down on a DatePicker that has the style GraphicalDatePickerStyle(). The things I've noticed are that if you have a minimum range added, in my case Date() it will flicker after a moment or two and shift downwards. I have also noticed if I remove that, it doesn't happen until the moment when I actually select a date on the picker itself. Here is a gif of that happening. In this example, I have removed the minimum selectable date.
The way that this view is being presented is via .offset modifier on the main view. This view is always present, though not visible and when you tap the add button it sets the offset back to 0. I stripped 99% of everything out and I still can't identify the problem.
Presentation Code (Minimal Reproducible, I Think)
struct TimeListView: View {
#ObservedObject var vm = TimerListViewModel()
#State var showsAddModal = false
var body: some View {
ZStack(alignment: .bottomTrailing) {
ScrollView {
LazyVStack {
ForEach (vm.events) { dueDate in
CountdownView(dueDate: dueDate)
.shadow(color: Color.black.opacity(0.4), radius: 3, x: 3, y: 3)
}
}
.padding(.horizontal)
}
.listStyle(PlainListStyle())
.background(Color.promptlyLightNavy)
Button(action: {
withAnimation {
showsAddModal.toggle()
}
}, label: {
Text("+")
.font(.custom("system", size: 50))
.frame(width: 70, height: 70)
.foregroundColor(.promptlyNavy)
.padding(.bottom, 7)
})
.background(
Circle()
.fill(Color.promptlyTeal)
.shadow(color: Color.black.opacity(0.4), radius: 3, x: 3, y: 3))
.padding()
Rectangle()
.fill(showsAddModal ? Color.promptlyNavy.opacity(0.8) : .clear)
.edgesIgnoringSafeArea(.all)
.onTapGesture {
withAnimation { showsAddModal.toggle() }
}
if showsAddModal {
AddCountdownView(dismissed: {
withAnimation { showsAddModal.toggle() }
}, add: { date, title in
vm.addEvent(event: Event(date: date, title: title))
}).transition(.move(edge: .bottom))
}
}
}
}
View That Is Causing The Problem (Minimal Reproducible)
struct AddCountdownView: View {
var dismissed: () -> ()
var add: ((date: Date, title: String)) -> ()
#State private var eventDate = Date.now
#State private var eventTitle = ""
var body: some View {
ZStack(alignment: .bottomLeading) {
Spacer()
VStack {
Divider()
RoundedRectangle(cornerRadius: 20)
.fill(Color.promptlyOrange)
.frame(width: 100, height: 4)
TextField("", text: $eventTitle)
.placeholder(when: eventTitle.isEmpty, placeholder: {
Text("Event Title").foregroundColor(.promptlyLightTeal)
})
.underlineTextField()
.font(.title)
DatePicker("asdf",selection: $eventDate, in: Date()...)
.applyTextColor(.white)
.datePickerStyle(GraphicalDatePickerStyle())
HStack(spacing: 50) {
Button(action: {
withAnimation { dismissed() }
}, label: {
Image(systemName: "trash")
.font(.title)
.frame(width: 100)
.background(
RoundedRectangle(cornerRadius: 10)
.fill(Color.promptlyTeal)
.shadow(color: Color.black.opacity(0.4), radius: 3, x: 3, y: 3)
.frame( height: 50)
)
})
Button(action: {
add((date: eventDate, title: eventTitle))
}, label: {
Image(systemName: "checkmark.seal")
.font(.title)
.frame(width: 100)
.background(
RoundedRectangle(cornerRadius: 10)
.fill(Color.promptlyTeal)
.shadow(color: Color.black.opacity(0.4), radius: 3, x: 3, y: 3)
.frame( height: 50)
)
})
}
.padding()
.foregroundColor(.promptlyNavy)
}
.background(Color.promptlyNavy)
.shadow(color: Color.black.opacity(0.4), radius: 3, x: 0, y: -3)
}
}
}

The problem is related directly to the way that DatePicker handles it's coloring. In my problem I was attempting to set the color by using a Hacky solution provided by a bunch of other people.
Hacky Solution (That's Bugged on iOS 15.X)
This is only bugged in this particular situation, where there's also a shadow added the the parent view.
#ViewBuilder func applyTextColor(_ color: Color) -> some View {
if UITraitCollection.current.userInterfaceStyle == .light {
self.colorInvert().colorMultiply(color)
} else {
self.colorMultiply(color)
}
}
Proper Solution (iOS 15.X)
DatePicker("asdf", selection: $eventDate, in: Date()...)
.datePickerStyle(GraphicalDatePickerStyle())
.accentColor(.promptlyOrange) // Sets Accent Color
.colorScheme(.dark) //Gets The white text.

Related

SwiftUI keyboard navigation in lists on MacOS

I'm trying to implement a list which can be navigated with arrow keys - up/down. I've created layout, but now I don't totally understand how(and where) to make up/down keys intercepted so I could add my custom logic. I already tried onMoveCommand with focusable but that did not work(wasn't firing at all)
Code I have - below
public var body: some View {
VStack(spacing: 0.0) {
VStack {
HStack(alignment: .center, spacing: 0) {
Image(systemName: "command")
.resizable()
.scaledToFit()
.frame(width: 20, height: 20)
.padding(.leading, 20)
.offset(x: 0, y: 1)
TextField("Search Commands", text: $state.commandQuery)
.font(.system(size: 20, weight: .light, design: .default))
.textFieldStyle(.plain)
.onReceive(
state.$commandQuery
.debounce(for: .seconds(0.1), scheduler: DispatchQueue.main)
) { val in
state.fetchMatchingCommands(val: val)
}
.padding(16)
.foregroundColor(Color(.systemGray).opacity(0.85))
.background(EffectView(.sidebar, blendingMode: .behindWindow))
}
}
Divider()
VStack(spacing: 0) {
List(state.filteredCommands.isEmpty && state.commandQuery.isEmpty ?
commandManager.commands : state.filteredCommands, selection: $selectedItem) { command in
VStack(alignment: .leading, spacing: 0) {
Text(command.title).foregroundColor(Color.white)
.padding(EdgeInsets.init(top: 0, leading: 10, bottom: 0, trailing: 0))
.frame(height: 10)
}.frame(maxWidth: .infinity, maxHeight: 15, alignment: .leading)
.listRowBackground(self.selectedItem == command ?
RoundedRectangle(cornerRadius: 5, style: .continuous)
.fill(Color(.systemBlue)) :
RoundedRectangle(cornerRadius: 5, style: .continuous)
.fill(Color.clear) )
.onTapGesture {
self.selectedItem = command
callHandler(command: command)
}.onHover(perform: { _ in self.selectedItem = command })
}.listStyle(SidebarListStyle())
}
}
.background(EffectView(.sidebar, blendingMode: .behindWindow))
.foregroundColor(.gray)
.edgesIgnoringSafeArea(.vertical)
.frame(minWidth: 600,
minHeight: self.state.isShowingCommandsList ? 400 : 28,
maxHeight: self.state.isShowingCommandsList ? .infinity : 28)
}
This is how it looks - and I want to make focus move between found list items
If I understand your question correctly, you want to use the arrow keys to "move" from the search TextField, to the list of items, and then navigate the list with the up/down arrow keys.
Try something simple like this example code, to monitor the up/down arrow keys, and take the appropriate action.
Adjust/tweak the logic to suit your needs.
import Foundation
import SwiftUI
import AppKit
struct ContentView: View {
let fruits = ["apples", "pears", "bananas", "apricot", "oranges"]
#State var selection: Int?
#State var search = ""
var body: some View {
VStack {
VStack {
HStack(alignment: .center, spacing: 0) {
Image(systemName: "command")
.resizable()
.scaledToFit()
.frame(width: 20, height: 20)
.padding(.leading, 20)
.offset(x: 0, y: 1)
TextField("Search", text: $search)
.font(.system(size: 20, weight: .light, design: .default))
.textFieldStyle(.plain)
}
}
Divider()
List(selection: $selection) {
ForEach(fruits.indices, id: \.self) { index in
Text(fruits[index]).tag(index)
}
}
.listStyle(.bordered(alternatesRowBackgrounds: true))
}
.onAppear {
NSEvent.addLocalMonitorForEvents(matching: [.keyDown]) { nsevent in
if selection != nil {
if nsevent.keyCode == 125 { // arrow down
selection = selection! < fruits.count ? selection! + 1 : 0
} else {
if nsevent.keyCode == 126 { // arrow up
selection = selection! > 1 ? selection! - 1 : 0
}
}
} else {
selection = 0
}
return nsevent
}
}
}
}

SwiftUI: Frame modifier in ZStack affecting other views

struct CircleTestView: View {
let diameter: CGFloat = 433
var body: some View {
ZStack {
Color(.yellow)
.ignoresSafeArea()
VStack {
Circle()
.fill(Color(.green))
.frame(width: diameter, height: diameter)
.padding(.top, -(diameter / 2))
Spacer()
}
VStack {
Spacer()
Button {} label: {
Color(.red)
.frame(height: 55)
.padding([.leading, .trailing], 16)
}
}
}
}
}
The code above creates the first image, yet for some reason if I remove the line the sets the frame for the Circle (ie. .frame(width: diameter, height: diameter)) I get the second image.
2.
I want the circle how it is in the first screen, and the button how it is in the second screen, but can't seem to achieve this. Somehow setting the frame of the Circle is affecting the other views, even though they're in a ZStack. Is this a bug with ZStacks, or am I misunderstanding how they work?
Lets call this one approach a:
struct CircleTestView: View {
let diameter: CGFloat = 433
var body: some View {
ZStack {
Color(.yellow)
.ignoresSafeArea()
VStack {
Circle()
.fill(Color(.green))
.frame(width: diameter, height: diameter)
.padding(.top, -(diameter / 2))
Spacer()
}
VStack {
Spacer()
Button {} label: {
Color(.red)
.frame(height: 55)
}
}
.padding(.horizontal, 16)
}
}
}
Lets call this one approach b:
struct CircleTestView: View {
let diameter: CGFloat = 433
var body: some View {
ZStack {
Color(.yellow)
.ignoresSafeArea()
VStack {
Circle()
.fill(Color(.green))
.offset(x: 0, y: -(diameter / 1.00))
// increment/decrement the offset by .01 example:
// .offset(x: 0, y: -(diameter / 1.06))
Spacer()
}
VStack {
Spacer()
Button {} label: {
Color(.red)
.frame(height: 55)
.padding([.leading, .trailing], 16)
}
}
}
}
}
A combination of the two approaches would land you at approach c.
Do any of these achieve what you are looking for?

I've searched and search SwiftUI Switch Case Menu Cycle?

I've found great content, But nothing too specific to my needs being swiftui is still new.
I need to support menu cycling with switch case in the latest swift and monterey for macos, no ios development. i need strings, variables, and custom graphics to make a menu title and current in need of up to 9 menus to cycle randomly from one to a random other without an if statement looping through all of the others first:
more info here: https://pastebin.com/VCnEmdBa
Additional information on needs:
I want to have the switch case cycle my nine menus, where i can be on any given one and the menu jump to the next random selection, right now it currently rotates in order no matter where i click.
import Foundation
import SwiftUI
import CoreData
import Combine
import PDFKit
import SceneKit
import WebKit
struct Cotharticren: App {
var body: some Scene {
WindowGroup {
ContentView()
}
}
}
struct Option: Hashable {
let title: String
let imageName: String
}
struct ContentView: View {
#State var currentOption = 0
let options: [Option] = [
.init(title: "DART Meadow", imageName: "sun.max.fill"),
.init(title: "Research", imageName: "flame"),
.init(title: "Navigation", imageName: "moon.stars.fill"),
.init(title: "Shelf", imageName: "archivebox"),
.init(title: "Chest" ,imageName: "shippingbox"),
.init(title: "Crate" ,imageName: "bonjour"),
.init(title: "Manufactoring", imageName: "gear"),
.init(title: "Warehouse", imageName: "archivebox.fill"),
.init(title: "Journal", imageName: "note.text"),
]
var body: some View {
VStack(alignment: .leading) {
HStack(alignment: .top) {
ScrollView( .vertical) {
NavigationView{
/*
List(1..<9)
{
Text("\($0)")
}
.listStyle(SidebarListStyle())
}
*/
ListView(options: options, currentSelection: $currentOption)
//Text(systemName: myItem.selectedImageName) + Text(myItem.whateverText)
switch (currentOption) {
case 1:
OrbitNodeView()
case 2:
ATM26NodeView()
case 3:
NozzleNodeView()
case 4:
EmptyView()
VStack(alignment: .center) {
Text("Chest")
.font(.largeTitle)
.bold()
.colorInvert()
}
case 5:
EmptyView()
VStack(alignment: .center) {
Text("Crate")
.font(.largeTitle)
.bold()
.colorInvert()
}
case 6:
EmptyView()
VStack(alignment: .center) {
Text("Manufactoring")
.font(.largeTitle)
.bold()
.colorInvert()
}
case 7:
EmptyView()
VStack(alignment: .center) {
Text("Warehouse")
.font(.largeTitle)
.bold()
.colorInvert()
}
case 8:
VStack(alignment: .center) {
Text("Journal")
.font(.largeTitle)
.bold()
.colorInvert()
.padding(.top, 60)
Image("articrenmeadowopacity")
.shadow(radius: 3)
WebView()
}
default:
MainView()
}
}.background(Color.white)
}
}
}
Spacer()
}
}
struct MainView: View {
var body: some View{
VStack(alignment: .leading) {
HStack(alignment: .bottom) {
Image("CotharticrenMainView")
.shadow(radius: 3)
}
}
.frame(width: 900, height: 800, alignment: .center)
Spacer()
}
}
struct ListView: View {
let options: [Option]
#Binding var currentSelection: Int
var body: some View{
VStack(alignment: .leading) {
HStack(alignment: .top) {
HStack(alignment: .top) {
VStack(alignment: .trailing) {
Circle()
.stroke(Color.init(red: 0.9, green: 0.95, blue: 0.2, opacity: 1), lineWidth: 2)
.alignmentGuide(HorizontalAlignment.myAlignment)
{ d in d[.leading] }
.alignmentGuide(VerticalAlignment.myAlignment)
{ d in d[.bottom] }
.frame(width: 50, height: 50)
Circle()
.stroke(Color.init(red: 0.25, green: 0.9, blue: 0.2, opacity: 1), lineWidth: 2)
.alignmentGuide(HorizontalAlignment.myAlignment)
{ d in d[.leading] }
.alignmentGuide(VerticalAlignment.myAlignment)
{ d in d[.bottom] }
.frame(width: 25, height: 25)
VStack(alignment: .leading) {
Circle()
.stroke(Color.init(red: 0.1, green: 0.1, blue: 1, opacity: 1), lineWidth: 2)
.alignmentGuide(HorizontalAlignment.myAlignment)
{ d in d[.leading] }
.alignmentGuide(VerticalAlignment.myAlignment)
{ d in d[.bottom] }
.frame(width: 75, height: 75)
}
}
}
HStack(alignment: .top) {
Image("DARTMeadowCSMwidthArtemis2by1")
.shadow(radius: 3)
.padding(.top, 10)
}
.padding(.top, 20)
.padding(.trailing, 10)
}.padding(.top, 20).padding(.leading, 10)
HStack(alignment: .center) {
VStack(alignment: .center) {
Image("arrow300")
HStack(alignment: .center) {
Text("You've never plotted an Edge?")
}
}
}.shadow(radius: 3)
VStack(alignment: .leading) {
let current = options[currentSelection]
ForEach(options, id: \.self) {option in
HStack {
Image(systemName: option.imageName)
//.resizable()
//.aspectRatio(contentMode: .fit)
.frame(width: 20)
Text(option.title)
.foregroundColor(current == option ? Color.blue : Color.white)
}
.padding(8)
.onTapGesture {
currentSelection += 1
if currentSelection == 9 {
currentSelection = 0
}
}
}
Spacer()
}.frame(width: 300, height: 800, alignment: .leading)
}
Spacer()
}
}
struct WebView: View {
var body: some View{
VStack(alignment: .leading) {
HStack(alignment: .bottom) {
}
}
.frame(width: 900, height: 800, alignment: .center)
Spacer()
}
}
You can add an Identifier to your Option class and use this for currentSelection, if you want to set an option, just set currentSelection to option.id:
Also:
1: If you want answers, it's best to format your code, before you post it (select in Xcode and ctrl+i should do it), so it's easy to read and understand
2: A minimal, reproducible example is not just posting your entire code, create an example, that contains only as much code as necessary to show the problem you're experiencing. The code I posted would be a better example, it will work without having to change anything. Your code includes references to objects that are not on here, so a possible helper would have to remove those, before he could even test your issue
here is a guide on how to create a minimal, reproducible example:
struct Option: Hashable, Identifiable {
// Identifier for Option !! MUST be unique
let id: Int
let title: String
let imageName: String
}
struct ContentView: View {
#State var currentOption: Int = 0
let options: [Option] = [
.init(id: 1, title: "DART Meadow", imageName: "sun.max.fill"),
.init(id: 2, title: "Research", imageName: "flame"),
.init(id: 3, title: "Navigation", imageName: "moon.stars.fill"),
]
var body: some View {
GeometryReader { geo in
HStack {
ListView(options: options, currentSelection: $currentOption)
.frame(width: geo.size.width / 2, height: geo.size.height)
switch (currentOption) {
case 1: Text("OrbitNodeView")
case 2: Text("ATM26NodeView")
case 3: Text("NozzleNodeView")
default: Text("MainView")
}
}
}
}
}
struct ListView: View {
let options: [Option]
#Binding var currentSelection: Int
var body: some View{
VStack(alignment: .leading) {
ForEach(options, id: \.self) {option in
HStack {
Image(systemName: option.imageName)
.frame(width: 20)
Text(option.title)
// Don't even have to use current = options[currentSelection] anymore:
.foregroundColor(currentSelection == option.id ? .accentColor : .primary)
}
.padding(8)
.onTapGesture {
// Set the currentSelection to the ID of the option
currentSelection = option.id
}
}
}
}
}

Animating a flashing bell in SwiftUI

I am having problems with making a simple systemIcon flash in SwiftUI.
I got the animation working, but it has a silly behaviour if the layout of
a LazyGridView changes or adapts. Below is a video of its erroneous behaviour.
The flashing bell stays in place but when the layout rearranges the bell
starts transitioning in from the bottom of the parent view thats not there anymore.
Has someone got a suggestion how to get around this?
Here is a working example which is similar to my problem
import SwiftUI
struct FlashingBellLazyVGrid: View {
#State var isAnimating = false
#State var showChart = true
var body: some View {
let columns = [GridItem(.adaptive(minimum: 300), spacing: 50, alignment: .center)]
VStack {
Button(action: {
showChart.toggle()
}) {
VStack {
Circle()
.fill(showChart ? Color.green : Color.red)
.shadow(color: Color.gray, radius: 5, x: 2, y: 2)
Text("Charts")
.foregroundColor(Color.primary)
}.frame(width: 150, height: 50)
}
ScrollView {
LazyVGrid (
columns: columns, spacing: 50
) {
ForEach(0 ..< 25) { item in
ZStack {
Rectangle()
.fill(Color.red)
.cornerRadius(15)
VStack {
HStack {
Text(/*#START_MENU_TOKEN#*/"Hello, World!"/*#END_MENU_TOKEN#*/)
Spacer()
Image(systemName: "bell.fill")
.foregroundColor(Color.yellow)
.opacity(self.isAnimating ? 1 : 0)
.animation(Animation.easeInOut(duration: 0.66).repeatForever(autoreverses: false))
.onAppear{ self.isAnimating = true }
}.padding(50)
if showChart {
Rectangle()
.fill(Color.green)
.frame(height: 200)
}
}
}
}
}
}
}
}
}
struct FlashingBellLazyVGrid_Previews: PreviewProvider {
static var previews: some View {
FlashingBellLazyVGrid()
}
}
how it looks like before you click the showChart button at the top
After you toggle the button it looks like the bells are erroneously moving into place from the bottom of the screen. and toggling it back to its original state doesn't resolve this bug subsequently.
[
Looks like the animation is basing itself off of the original size of the view. In order to trick it into recognizing the new view size, I used .id(UUID()) on the outside of the grid. In a real world application, you'd probably want to be careful to store this ID somewhere and only refresh it when needed -- not on every re-render like I'm doing:
struct FlashingBellLazyVGrid: View {
#State var showChart = true
let columns = [GridItem(.adaptive(minimum: 300), spacing: 50, alignment: .center)]
var body: some View {
VStack {
Button(action: {
showChart.toggle()
}) {
VStack {
Circle()
.fill(showChart ? Color.green : Color.red)
.shadow(color: Color.gray, radius: 5, x: 2, y: 2)
Text("Charts")
.foregroundColor(Color.primary)
}.frame(width: 150, height: 50)
}
ScrollView {
LazyVGrid (
columns: columns, spacing: 50
) {
ForEach(0 ..< 25) { item in
ZStack {
Rectangle()
.fill(Color.red)
.cornerRadius(15)
VStack {
SeparateComponent()
if showChart {
Rectangle()
.fill(Color.green)
.frame(height: 200)
}
}
}
}
}
.id(UUID()) //<-- Here
}
}
}
}
struct SeparateComponent : View {
#State var isAnimating : Bool = false
var body: some View {
HStack {
Text("Hello, World!")
Spacer()
Image(systemName: "bell.fill")
.foregroundColor(Color.yellow)
.opacity(self.isAnimating ? 1 : 0)
.animation(Animation.easeInOut(duration: 0.66).repeatForever(autoreverses: false))
.onAppear{
self.isAnimating = true
}
}
.padding(50)
}
}
I also separated out the blinking component into its own view, since there were already problematic things happening with the existing logic with onAppear, which wouldn't affect newly-scrolled-to items correctly. This may need refactoring for your particular case as well, but this should get you started.

Style the picker in SwiftUI

I'm doing a login form in Swiftui, however when i try to add a form that contain picker for age the style get messed up and since im new to SwiftUI i didn't know how can i fix this problem?
How can i fix the picker style so it addapte with the Nickname field?
here it is the code that i tried:
#State var nickName: String = ""
#State var age: Int = 18
var body: some View {
VStack {
HStack {
Image(systemName: "person.circle.fill")
.foregroundColor(Color("ChatiwVeryLightBlue"))
.frame(width: 44, height: 44)
.background(Color.white)
.clipShape(RoundedRectangle(cornerRadius: 16, style: .continuous))
.shadow(color: Color.black.opacity(0.15), radius: 5, x: 0, y: 5)
.padding(.all, 10)
TextField("Nickname", text: $nickName)
.frame(maxWidth: .infinity)
.frame(height: 50)
}
Divider().padding(.leading, 80)
HStack {
Image(systemName: "person.circle.fill")
.foregroundColor(Color("ChatiwVeryLightBlue"))
.frame(width: 44, height: 44)
.background(Color.white)
.clipShape(RoundedRectangle(cornerRadius: 16, style: .continuous))
.shadow(color: Color.black.opacity(0.15), radius: 5, x: 0, y: 5)
.padding(.all, 10)
Form {
Section {
Picker(selection: $age, label: Text("Picker")) {
ForEach(18 ..< 99, id: \.self) { index in
Text("\(index)").tag(1)
}
}
}
}
}
// Age
// Country
// Male/Female
}
.background(BlurView(blurType: .systemMaterial))
.clipShape(RoundedRectangle(cornerRadius: 16, style: .continuous))
.padding()
.offset(y: 400)
Not sure if you are still working on this project, but here is a neat workaround:
struct ContentView: View {
#State private var age = 18
var body: some View {
NavigationView {
VStack {
Divider()
HStack {
NavigationLink(destination: AgePickerView(age: $age)) {
HStack {
Text("Age")
Spacer()
Text("\(age) >")
.padding(.trailing)
}
}
}
.foregroundColor(.gray)
Divider()
}
.padding(.leading, 80)
}
}
struct AgePickerView: View {
#Binding var age: Int
var body: some View {
List(18..<99) { index in
Button(action: {
self.age = index
}) {
HStack {
Text("\(index)")
Spacer()
if index == age {
Image(systemName: "checkmark")
.foregroundColor(.blue)
}
}
}
}
}
}
}
To get the same NavigationView style but remove the Form style, you need to wrap a NavigationLink in a NavigationView.
The little > sign I put at the end of the Text view in the NavigationLink doesn't perfectly match the one in the Form style, so you can look around for an image that is more similar if you would like.
The AgePickerView is pretty straightfoward; it's just a list of buttons that are modifying a binding to the original state variable in ContentView through tap gestures. The checkmark is there to imitate a Picker view.
The best part of this is that you don't have to worry about all that index shifting with a picker.