I have a Text that when pressed, a pop-up with a DatePicker is shown.
The pop-up is a View that contains the DatePicker from which I want to select a date. However, when I select the desired date, it doesn't close the pop-up and return the date:
Text("Selecciona vencimiento")
.font(Font.appFontBold(size: 16))
.foregroundColor(.textColor)
.frame(maxWidth:.infinity,alignment: .leading)
.padding(.top,5)
DatePicker("", selection: $selectedDate, displayedComponents: [.date])
.datePickerStyle(GraphicalDatePickerStyle()) .labelsHidden()
.id(selectedDate)
.environment(\.locale, Locale.preferredLocale())
.onChange(of: selectedDate, perform: { value in
if self.isShowing {
self.formatedDate = value.toString(format:"dd/MM/yyyy")
self.isShowing = false
}
})
I tried to add a Gesture Tap to DatePicker.
I want to add that I also have the following:
struct GlobalTimerView<Content: View>: View {
#Environment(\.globalTimerKey) var globalTimer
let content: Content
init(#ViewBuilder content: () -> Content) {
self.content = content()
}
var body: some View {
ZStack {
content
}.onAppear {
globalTimer.reset()
}.onTapGesture
{
globalTimer.reset()
}
}
}
This block of code helps to reset the remaining time on each user interaction, but when the user taps onTapGesture is executed which resets the time and doesn't let the DatePicker tap.
Is there a better way to do it, either change the way time is handled or something I need to fix in the DatePicker?
Related
I have passed a constant value to Toggle since I want to perform certain acctions on value change of toggle and it needs to be done on Tap Gesture as internally I might need to change the value of toggle as well. But hus code is not working
#State private var toggle = false
var body: some View {
VStack {
Toggle(isOn: .constant(toggle)) {
Text("Hello World")
}
.padding()
.onTapGesture {
print("Tapped")
self.toggle.toggle()
}
}
}
}
Toggle(isOn: Binding(
get:{isToggled},
set:{v in
isToggled = v
customAction()
})) {
Text("Status")
}
Custom action code does not get triggered if the state is modified. But will be triggered on UI interaction
I am trying to inset the bottom of a list by the height of the keyboard when the keyboard shows and the list is scrolled to the bottom. I know this question has been asked in different scenarios but I haven't found any proper solution yet. This is specifically for a chat app and the simple code below demonstrates the problem:
#State var text: String = ""
#FocusState private var keyboardVisible: Bool
var body: some View {
NavigationView {
VStack {
List {
ForEach(1...100, id: \.self) { item in
Text("\(item)")
}
}
.navigationTitle("Conversations")
.toolbar {
ToolbarItem(placement: .navigationBarTrailing) {
Button {
keyboardVisible = false
} label: {
Text("Hide Keyboard")
}
}
}
ZStack {
Color.red
.frame(height: 44)
TextEditor(text: $text)
.frame(height: 32)
.focused($keyboardVisible)
}
}
}
}
If you scroll to the end and tap in the textEditor, the text editor moves up with the keyboard as expected, but the list doesn't move the content up. I wonder how I can achieve this and have it move up smoothly with the keyboard.
You don't mention trying it, but this is exactly what we have a ScrollViewReader() for. There is a bit of an issue using it in this case, that can be worked around. The issue is that keyboardVisible changes BEFORE the keyboard is fully up. If you scroll at that point, you will cut off the bottom of the List entries. So, we need to delay the reader with DispatchQueue.main.asyncAfter(deadline:). This causes enough delay that when the reader actually reads the position, the keyboard is at full height, and the scroll to the bottom does its job.
#State var text: String = ""
#FocusState private var keyboardVisible: Bool
var body: some View {
NavigationView {
VStack {
ScrollViewReader { scroll in
List {
ForEach(1...100, id: \.self) { item in
Text("\(item)")
}
}
.onChange(of: keyboardVisible, perform: { _ in
if keyboardVisible {
withAnimation(.easeIn(duration: 1)) {
scroll.scrollTo(100) // this would be your array.count - 1,
// but you hard coded your ForEach
}
// The scroll has to wait until the keyboard is fully up
// this causes it to wait just a bit.
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
// this helps make it look deliberate and finished
withAnimation(.easeInOut(duration: 1)) {
scroll.scrollTo(100) // this would be your array.count - 1,
// but you hard coded your ForEach
}
}
}
})
.navigationTitle("Conversations")
.toolbar {
ToolbarItem(placement: .navigationBarTrailing) {
Button {
keyboardVisible = false
} label: {
Text("Hide Keyboard")
}
}
}
ZStack {
Color.red
.frame(height: 44)
TextEditor(text: $text)
.frame(height: 32)
.focused($keyboardVisible)
}
}
}
}
}
Edit:
I changed the code to scroll twice. The first starts the scroll immediately, and the second scrolls it after the keyboard is up to finish the job. It starts quickly and ends smoothly. I also left the comments in the code for the next person; you don't need them.
I have two NavigationLink in a cell of my List
I want to go to destination1 when I tap once,and go to destination2 when I tap twice.
So I added two tap gesture to control the navigation.
But when I tap,there are two questions:
1 The tap gesture block won't be called.
2 The two navigation link will be both activated automatically even if they are behind a TextView.
The real effect is: Tap the cell -> go to Destination1-> back to home -> go to Destination2 -> back to home
Here is my code :
struct MultiNavLink: View {
#State var mb_isActive1 = false;
#State var mb_isActive2 = false;
var body: some View {
return
NavigationView {
List {
ZStack {
NavigationLink("", destination: Text("Destination1"), isActive: $mb_isActive1)
NavigationLink("", destination: Text("Destination2"), isActive: $mb_isActive2)
Text("Single tap::go to destination1\nDouble tap,go to destination2")
}
.onTapGesture(count: 2, perform: {()->Void in
NSLog("Double tap::to destination2")
self.mb_isActive2 = true
}).onTapGesture(count: 1, perform: {()->Void in
NSLog("Single tap::to destination1")
self.mb_isActive1 = true
})
}.navigationBarTitle("MultiNavLink",displayMode: .inline)
}
}
}
I have tried remove the List element,then everything goes as I expected.
It seems to be the List element that makes everything strange.
I found this question:SwiftUI - Two buttons in a List,but the situation is different from me.
I am expecting for your answer,thank you very much...
Try the following approach - the idea is to hide links in background of visible content and make them inactive for UI, but activated programmatically.
Tested with Xcode 12 / iOS 14.
struct MultiNavLink: View {
var body: some View {
return
NavigationView {
List {
OneRowView()
}.navigationBarTitle("MultiNavLink", displayMode: .inline)
}
}
}
struct OneRowView: View {
#State var mb_isActive1 = false
#State var mb_isActive2 = false
var body: some View {
ZStack {
Text("Single tap::go to destination1\nDouble tap,go to destination2")
}
.frame(maxWidth: .infinity, maxHeight: .infinity)
.contentShape(Rectangle())
.background(Group {
NavigationLink(destination: Text("Destination1"), isActive: $mb_isActive1) {
EmptyView() }
.buttonStyle(PlainButtonStyle())
NavigationLink(destination: Text("Destination2"), isActive: $mb_isActive2) {
EmptyView() }
.buttonStyle(PlainButtonStyle())
}.disabled(true))
.highPriorityGesture(TapGesture(count: 2).onEnded {
self.mb_isActive2 = true
})
.onTapGesture(count: 1) {
self.mb_isActive1 = true
}
}
}
Navigation link has a initializer that takes a binding selection and whenever that selection is set to the value of the NavigationLink tag, the navigation link will trigger.
As a tip, if the app can't differentiate and identify your taps, and even with two taps, still the action for one-tap will be triggered, then you can use a simultaneous gesture(.simultaneousGesture()) modifier instead of a normal gesture(.gesture()) modifier.
struct someViewName: View {
#State var navigationLinkTriggererForTheFirstOne: Bool? = nil
#State var navigationLinkTriggererForTheSecondOne: Bool? = nil
var body: some View {
VStack {
NavigationLink(destination: SomeDestinationView(),
tag: true,
selection: $navigationLinkTriggererForTheFirstOne) {
EmptyView()
}
NavigationLink(destination: AnotherDestinationView(),
tag: true,
selection: $navigationLinkTriggererForTheSecondOne) {
EmptyView()
}
NavigationView {
Button("tap once to trigger the first navigation link.\ntap twice to trigger the second navigation link.") {
// tap once
self.navigationLinkTriggererForTheFirstOne = true
}
.simultaneousGesture(
TapGesture(count: 2)
.onEnded { _ in
self.navigationLinkTriggererForTheSecondOne = true
}
)
}
}
}
}
I'm getting some odd animation behaviour with DatePickers in a SwiftUI form. A picture is worth a thousand words, so I'm sure a video is worth a million words: https://imgur.com/a/UHXqXOh
I'm trying to get the date picker to expand and then collapse within the form, exactly like the behaviour when creating a new event in Calendar.app
What is happening for me is:
Any expanding item in a Section (other than the last one) will open normally, but when it closes the expanded part slides down and fades, instead of sliding up and fading.
The last item in the section slides correctly but doesn't fade at all. It simply appears and then disappears at the start/end of the transition
These behaviours only happen if there is a non-DatePicker element (e.g. Text, Slider) somewhere in the form (doesn't have to be in that particular section)
Here's my ContentView:
struct ContentView: View {
#State var date = Date()
#State var isDateShown = false
var body: some View {
Form {
Section(header: Text("Title")) {
DatePicker("Test", selection:$date)
DatePicker("Test", selection:$date)
Text("Pick a date").onTapGesture {
withAnimation {
self.isDateShown.toggle()
}
}
if(isDateShown) {
DatePicker("", selection: $date).datePickerStyle(WheelDatePickerStyle()).labelsHidden()
}
}
Section(header: Text("hello")) {
Text("test")
}
}
}
}
Happy to provide anything else required
Here are two possible workarounds for iOS <14: 1) simple one is to disable animation at all, and 2) complex one is to mitigate incorrect animation by injecting custom animatable modifier
Tested both with Xcode 11.4 / iOS 13.4
1) simple solution - wrap DatePicker into container and set animation to nil
VStack {
DatePicker("Test", selection:$date).id(2)
}.animation(nil)
2) complex solution - grab DatePicker changing frame using a) view preference reader ViewHeightKey and b) animate this frame explicitly using AnimatingCellHeight from my other solutions.
struct TestDatePickersInForm: View {
#State var date = Date()
#State var isDateShown = false
#State private var height = CGFloat.zero
var body: some View {
Form {
Section(header: Text("Title")) {
// demo of complex solution
VStack {
DatePicker("Test", selection:$date).id(1)
.background(GeometryReader {
Color.clear.preference(key: ViewHeightKey.self,
value: $0.frame(in: .local).size.height) })
}
.onPreferenceChange(ViewHeightKey.self) { self.height = $0 }
.modifier(AnimatingCellHeight(height: height))
.animation(.default)
// demo of simple solution
VStack {
DatePicker("Test", selection:$date).id(2)
}.animation(nil)
Text("Pick a date").onTapGesture {
withAnimation {
self.isDateShown.toggle()
}
}
if(isDateShown) {
DatePicker("", selection: $date).datePickerStyle(WheelDatePickerStyle()).labelsHidden().id(3)
}
}
Section(header: Text("hello")) {
Text("test")
}
}
}
}
Funny enough.. with the new beta, they apparently changed the DatePicker.
So if you have no problem with iOS 14+ only...
closest solution is to move the datepickers to its own sections
Form {
Section(header: Text("Title")) {
DatePicker(selection:$date1, label: {Text("Test")} )
}
DatePicker("Test", selection:$date2)
Section{
Text("Pick a date").onTapGesture {
withAnimation {
self.isDateShown.toggle()
}
}
if(isDateShown) {
DatePicker("", selection: $date3).datePickerStyle(WheelDatePickerStyle()).labelsHidden()
}
}
Section(header: Text("Hello")){
Text("Hello")
}
}
Following tutorials, I have the following code to show a tab view with 3 tab items all with an icon on them, When pressed they navigate between the three different views. This all works fine, however, I want to be able to handle the selection and only show views 2 or 3 if certain criteria are met.
Is there a way to get the selected value and check it then check my own criteria and then show the view is criteria is met, or show an alert if it is not saying they can't use that view at the moment.
Essentially I want to be able to intercept the selection value before it switches out the view, maybe I need to rewrite all of this but this is the functionality I'm looking for as this is how I had my previous app working using the old framework.
#State private var selection = 1
var body: some View
{
TabbedView(selection: $selection)
{
View1().tabItemLabel(
VStack
{
Image("icon")
Text("")
})
.tag(1)
View2().tabItemLabel(
VStack
{
Image("icon")
Text("")
}).tag(2)
View3().tabItemLabel(
VStack
{
Image("icon")
Text("")
}).tag(3)
}
}
You can do it by changing the value of selection on tap. You can use .onAppear() method for a particular tab to check your condition:
#State private var selection = 1
var conditionSatisfied = false
var body: some View
{
TabbedView(selection: $selection)
{
View1().tabItemLabel(
VStack
{
Image("icon")
Text("")
})
.tag(1)
View2().tabItemLabel(
VStack
{
Image("icon")
Text("")
}).tag(2)
.onAppear() {
if !conditionSatisfied {
self.selection = 1
}
}
View3().tabItemLabel(
VStack
{
Image("icon")
Text("")
}).tag(3)
.onAppear() {
if !conditionSatisfied {
self.selection = 1
}
}
}
}