If after activating TextField I press Open link button, NavigationLink will be opened. After that if I return back to previous screen, VStack with TextField will stay in the middle of the screen, because bottom SafeArea will be expanded by keyboard. This happening if first view in ZStack is ScrollView. It should go back to bottom after keyboard is disabled. How can I fix that?
struct ContentView: View {
#State private var text = ""
var body: some View {
NavigationStack {
ZStack(alignment: .bottom) {
ScrollView {
Color.green.opacity(0.2)
.frame(height: 1000)
}
.ignoresSafeArea(.keyboard)
VStack {
TextField("", text: $text, prompt: Text("Input"))
.textFieldStyle(.roundedBorder)
.padding()
NavigationLink("Open link") {
Text("Details view")
}
}
.background { Color.red }
.background(ignoresSafeAreaEdges: .bottom)
}
}
}
}
You can try using the #FocusState property wrapper. Add 3 following command lines:
//1
#FocusState private var nameIsFocused: Bool
//2
.focused($nameIsFocused)
//3
.simultaneousGesture(TapGesture().onEnded({ _ in
nameIsFocused = false
}))
The code you wrote looks like this:
struct ContentView: View {
#State private var text = ""
//1
#FocusState private var nameIsFocused: Bool
var body: some View {
NavigationStack {
ZStack(alignment: .bottom) {
ScrollView {
Color.green.opacity(0.2)
.frame(height: 1000)
}
.ignoresSafeArea(.keyboard)
VStack {
TextField("", text: $text, prompt: Text("Input"))
//2
.focused($nameIsFocused)
.textFieldStyle(.roundedBorder)
.padding()
NavigationLink("Open link") {
Text("Details view")
}
//3
.simultaneousGesture(TapGesture().onEnded({ _ in
nameIsFocused = false
}))
}
.background { Color.red }
.background(ignoresSafeAreaEdges: .bottom)
}
}
}
}
Result:
Hope it is useful for you!
Related
I try to switch from one screen to another by pressing a button (see full code below). The switch from the first view to the second view (and vice versa) works, but no animation is taking place.
Why does this behavior happen?
Full code:
struct ContentView: View {
#AppStorage("firstViewActive") var isFirstViewActive: Bool = true
var body: some View {
if isFirstViewActive {
FirstView()
} else {
SecondView()
}
}
}
struct FirstView: View {
#AppStorage("firstViewActive") var isFirstViewActive: Bool = true
var body: some View {
ZStack {
Color(.red).ignoresSafeArea(.all, edges: .all)
VStack {
Spacer()
Text("This is the first view")
Spacer()
Button {
withAnimation {
isFirstViewActive = false
}
} label: {
Text("Go to second view")
}
Spacer()
}
}
}
}
struct SecondView: View {
#AppStorage("firstViewActive") var isFirstViewActive: Bool = false
var body: some View {
ZStack {
Color(.blue).ignoresSafeArea(.all, edges: .all)
VStack {
Spacer()
Text("This is the second view")
Spacer()
Button {
withAnimation {
isFirstViewActive = true
}
} label: {
Text("Go to first view")
}
Spacer()
}
}
}
}
The problem is with
#AppStorage("firstViewActive") var isFirstViewActive: Bool = true
If you change that to
#State var isFirstViewActive: Bool = true
and use #Binding in subviews, you will get the default animations.
In iOS 16, there seems to be a problem with #AppStorage vars and animation. But you can refer to this workaround
I have a requirement where we need to keep close active unless bottom sheet is not closed. I have seen similar implementations in iOS maps where search keyboard is opened until the view is fully dismissed.
What I have currently :-
What I want :- https://drive.google.com/file/d/1SmXniFp0ZTF5igMzk6gk3eflCeP1xjn6/view?usp=share_link
This is code which i use to to present iOS native sheet :-
struct ContentView: View {
#State var isPresented = false
var body: some View {
VStack {
Image(systemName: "globe")
.imageScale(.large)
.foregroundColor(.accentColor)
Text("Hello, world!")
}
.onTapGesture {
isPresented.toggle()
}
.sheet(isPresented: $isPresented) {
BottomSheetViewRepresentable(content: {
NavigationView {
DemoView()
}
.navigationTitle("Hey")
}, detents: [.large()])
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
struct DemoView: View {
var body: some View{
if #available(iOS 16.0, *) {
ZStack {
TextField("", text: .constant("Hey"))
}
.scrollDismissesKeyboard(.never)
} else {
// Fallback on earlier versions
Color.yellow
}
}
}
I see you have added .scrollDismissesKeyboard(.never) on the ZStack which seems not working.
Try wrapping content in a List or ScrollView, then set scrollDismissesKeyboard to never.
Examples -
var body: some View {
ScrollView {
VStack {
TextField("Name", text: $username)
.textFieldStyle(.roundedBorder)
TextEditor(text: $bio)
.frame(height: 400)
.border(.quaternary, width: 1)
}
.padding(.horizontal)
}
.scrollDismissesKeyboard(.never)
}
List example -
struct KeyboardDismissExample: View {
#State private var text = ""
var body: some View {
List {
ForEach(0..<100) { i in
TextField(("Item \(i)"), text: .constant(""))
}
}
.scrollDismissesKeyboard(.never)
}
}
I have a basic window with an input field (page 1), on top of it appears a popup window (page 2), inside of which there is also an input fields and buttons, which, when clicked, will bring up a small window with an input field (page 3). If there is no "Done" on the keyboard, the interface functions normally. If you add a "Done" button, it turns out that its color changes from system color blue to gray when moving from page 2 to page 3. Experimenting and wondering why this is so, I found that the toolbar on page 1 is responsible for the color of the button on page 3... If you change the color of the button on the toolbar on page 1 - it will change on the toolbar on page 3, and page 2 will not be affected. Also, adding buttons causes error: "[LayoutConstraints] Unable to simultaneously satisfy constraints." I want to understand why setting the button on the keyboard for Page 1, I also get a button when I type on Page 3? And why is it grayed out and not working? Why if I change the color for the button on Page 1, does it also change for that gray button on Page 3?
A small representative sample:
ContentView
import SwiftUI
struct ContentView: View {
#State private var bloodClucoseLvl: String = ""
#State private var isSheetShown: Bool = false
#FocusState private var focusField: Bool
var body: some View {
NavigationView {
List {
Section("Add your current blood glucose lvl") {
TextField("5,0 mmol/l", text: $bloodClucoseLvl)
.focused($focusField)
}
Section("Add food or drink") {
Button(action:{
isSheetShown.toggle()
}, label:{
HStack{
Text("Add")
Image(systemName: "folder.badge.plus")
}
})
.sheet(isPresented: $isSheetShown) {
addFoodButton()
}
}
}
.navigationTitle("Page 1 - General")
.toolbar{
ToolbarItem(placement: .keyboard) {
HStack {
Spacer()
Button(action: {
focusField = false
}) {
Text("Done")
}
}
}
}
.ignoresSafeArea(.keyboard)
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
addFoodButton
import SwiftUI
struct addFoodButton: View {
#State private var selectedFood: String = ""
#State public var addScreen: Bool = false
var body: some View {
ZStack {
NavigationView {
List {
Section("or choose from category"){
NavigationLink(destination: Alcohol(addScreen: $addScreen)){
Text("Alcohol")
}
}
}
.listStyle(.insetGrouped)
.searchable(text: $selectedFood, prompt: "Search by word")
.navigationTitle("Page 2 - Search in DB")
}
if addScreen{
addScreenView(addScreen: $addScreen)
}
}
}
}
struct Alcohol: View {
#State private var searchInsideCategory: String = ""
#Binding var addScreen: Bool
var body: some View {
List {
Button(action: {addScreen.toggle()}){
Text("Light beer")
}
}
.navigationTitle("Page 2 - Choose beer")
.searchable(text: $searchInsideCategory, prompt: "Search inside a category")
}
}
struct addFoodButton_Previews: PreviewProvider {
static var previews: some View {
addFoodButton()
}
}
addScreenView
import SwiftUI
struct addScreenView: View {
#Binding var addScreen: Bool
#State private var gram: String = ""
var body: some View {
ZStack{
Color.black.opacity(0.2).ignoresSafeArea()
VStack(spacing:0){
Text("Page 3 - Add an item")
.bold()
.padding()
Divider()
VStack(){
TextField("gram", text: $gram)
.padding(.leading, 16)
.padding(.trailing, 16)
.keyboardType(.numberPad)
Rectangle()
.frame(height: 1)
.padding(.leading, 16)
.padding(.trailing, 16)
}.padding()
Divider()
HStack(){
Button(action: {
addScreen.toggle()
}){
Text("Cancel").frame(minWidth:0 , maxWidth: .infinity)
}
Divider()
Button(action: {
addScreen.toggle()
}){
Text("Save").frame(minWidth:0 , maxWidth: .infinity)
}
}.frame(height: 50)
}
.background(Color.white.cornerRadius(10))
.padding([.leading, .trailing], 15)
}
}
}
struct addScreenView_Previews: PreviewProvider {
static var previews: some View {
addScreenView(addScreen: .constant(true))
}
}
I'm creating a simple iOS app with SwiftUI, and I'd like to change my view's background color when switch toggle change.
My code
struct ContentView: View {
#State private var isOnLight: Bool = false
var body: some View {
VStack {
Toggle(isOn: $isOnLight) {
Text("Switch")
.font(.title)
.foregroundColor(.gray)
}
if isOnLight {
}
}.padding()
}
}
For background colors you can use the ZStack like this and with one line ifs then decide on the color
struct ContentView: View {
#State private var isOnLight: Bool = false
var body: some View {
ZStack {
isOnLight ? Color.blue : Color.red
VStack {
Toggle(isOn: $isOnLight) {
Text("Switch")
.font(.title)
.foregroundColor(.gray)
}
}
.padding()
}
}
}
To learn about how to use ternary operator in SwiftUI you can watch this video
You just need to embed your VStack inside a ZStack, where the back layer is a color that changes every time isOnLight changes.
Like this:
struct Example: View {
#State private var isOnLight: Bool = false
#State private var color: Color = .white
var body: some View {
ZStack {
color
.ignoresSafeArea()
VStack {
Toggle(isOn: $isOnLight) {
Text("Switch")
.font(.title)
.foregroundColor(.gray)
}
}
.padding()
}
.onChange(of: isOnLight) { value in
if value {
color = .yellow
} else {
color = .white
}
}
}
}
I can navigate to next screen by using NavigationButton (push) or present with PresentationButton (present) but i want to push when i tap on Buttton()
Button(action: {
// move to next screen
}) {
Text("See More")
}
is there a way to do it?
You can do using NavigationLink
Note: Please try in real device. in simulator sometimes not work properly.
struct MasterView: View {
#State var selection: Int? = nil
var body: some View {
NavigationView {
VStack {
NavigationLink(destination: DetailsView(), tag: 1, selection: $selection) {
Button("Press me") {
self.selection = 1
}
}
}
}
}
}
struct DetailsView: View {
#Environment(\.presentationMode) var presentation
var body: some View {
Group {
Button("Go Back") {
self.presentation.wrappedValue.dismiss()
}
}
}
}
As you can see to display the new view, add the NavigationLink with isActive: $pushView using <.hidden()> to hide the navigation "arrow".
Next add Text("See More") with tapGesture to make the text respond to taps. The variable pushView will change (false => true) when you click "See More" text.
import SwiftUI
struct ContentView: View {
#State var pushView = false
var body: some View {
NavigationView {
List {
HStack{
Text("test")
Spacer()
NavigationLink(destination: NewView(), isActive: $pushView) {
Text("")
}.hidden()
.navigationBarTitle(self.pushView ? "New view" : "default view")
Text("See More")
.padding(.trailing)
.foregroundColor(Color.blue)
.onTapGesture {
self.pushView.toggle()
}
}
}
}
}
}
struct NewView: View {
var body: some View {
Text("New View")
}
}
ContentView picture
NewView picture
To tap on button and navigate to next screen,You can use NavigationLink like below
NavigationView{
NavigationLink(destination: SecondView()) {
Text("Login")
.padding(.all, 5)
.frame(minWidth: 0, maxWidth: .infinity,maxHeight: 45, alignment: .center)
.foregroundColor(Color.white)
}
}
You can use NavigationLink to implement this:
struct DetailsView: View {
var body: some View {
VStack {
Text("Hello world")
}
}
}
struct ContentView: View {
#State var selection: Int? = nil
var body: some View {
NavigationView {
VStack {
NavigationLink(destination: DetailsView(), tag: 1, selection: $selection) {
Button("Press me") {
self.selection = 1
}
}
}
}
}
}