Pretty simple question - I'm just curious if there is any perceivable difference between Spacer() and Color.clear in SwiftUI
If you measure the screen, then yes.
I notice a ~2px difference when using both as in this example:
struct ContentView: View {
var body: some View {
HStack {
Spacer()
Text("First")
Spacer()
Text("Second")
Spacer()
}
}
}
Which generates:
(the middle space takes around 84px)
Now using Color.clear:
struct ContentView: View {
var body: some View {
HStack {
Color.clear
Text("First")
Color.clear
Text("Second")
Color.clear
}
}
}
Output:
Notice that the "First" and "Second" strings don't touch the vertical guide anymore...
(the middle "space" now takes around 86px)
Not a huge difference, but I would stick to Spacer.
(xScope is our friend)
Related
I am using TextField in my view and when it becomes the first responder, it lefts the view as shown in the below GIF.
Is there any way I can get rid of this behavior?
Here is my code
NavigationView(content: {
ZStack{
MyTabView(selectedIndex: self.$index)
.view(item: self.item1) {
NewView(title: "Hello1").navigationBarTitle("")
.navigationBarHidden(true)
}
.view(item: self.item2) {
NewView(title: "Hello2").navigationBarTitle("")
.navigationBarHidden(true)
}
.view(item: self.item3) {
NewView(title: "Hello3").navigationBarTitle("")
.navigationBarHidden(true)
}
}.navigationBarHidden(true)
.navigationBarTitle("")
}).ignoresSafeArea(.keyboard, edges: .bottom)
// New View
struct NewView:View {
#State var text:String = ""
var title:String
var body: some View {
VStack {
Spacer()
Text("Hello")
TextField(title, text: self.$text)
.textFieldStyle(RoundedBorderTextFieldStyle())
}.padding()
.onAppear {
debugPrint("OnApper \(self.title)")
}
}
}
For .ignoresSafeArea to work you need to fill all the available area (eg. by using a Spacer).
The following will not work (no Spacers, just a TextField):
struct ContentView: View {
#State var text: String = ""
var body: some View {
VStack {
TextField("asd", text: self.$text)
.textFieldStyle(RoundedBorderTextFieldStyle())
}
.ignoresSafeArea(.keyboard, edges: .bottom)
}
}
However, it will work when you add Spacers (fill all the available space):
struct ContentView: View {
#State var text: String = ""
var body: some View {
VStack {
Spacer()
TextField("asd", text: self.$text)
.textFieldStyle(RoundedBorderTextFieldStyle())
Spacer()
}
.ignoresSafeArea(.keyboard, edges: .bottom)
}
}
If you don't want to use Spacers you can also use a GeometryReader:
struct ContentView: View {
#State var text: String = ""
var body: some View {
GeometryReader { _ in
...
}
.ignoresSafeArea(.keyboard, edges: .bottom)
}
}
You should apply the modifier on the ZStack, NOT the NavigationView
NavigationView(content: {
ZStack{
,,,
}.navigationBarHidden(true)
.navigationBarTitle("")
.ignoresSafeArea(.keyboard, edges: .bottom) // <- This line moved up
})
Full working example:
struct ContentView: View {
#State var text = ""
var body: some View {
VStack{
Spacer()
Text("Hello, World")
TextField("Tap to test keyboard ignoring", text: $text)
.textFieldStyle(RoundedBorderTextFieldStyle())
}
.padding()
.ignoresSafeArea(.keyboard, edges: .bottom)
}
}
What eventually worked for me, combining answers posted here and considering also this question, is the following (Xcode 12.4, iOS 14.4):
GeometryReader { _ in
VStack {
Spacer()
TextField("Type something...", text: $value)
Spacer()
}.ignoresSafeArea(.keyboard, edges: .bottom)
}
Both spacers are there to center vertically the textfield.
Using only the GeometryReader or the ignoresSafeArea modifier didn't do the trick, but after putting them all together as shown above stopped eventually the view from moving up upon keyboard appearance.
That's what I figured out:
GeometryReader { _ in
ZStack {
//PUT CONTENT HERE
}.frame(width: UIScreen.main.bounds.width, height: UIScreen.main.bounds.height)
}
It seems to work for me. In this case you do not need to check iOS 14 availability.
I am experiencing very odd behavior in SwiftUI 2.0 and iOS14.
When the keyboard appears on the screen, the OnAppear method of other tab's view called automatically.
However, this works fine Xcode 11.7
Here is the issue in action.
Here is the code which produces the above error.
struct ContentView: View {
var body: some View {
TabView {
DemoView(screenName: "Home")
.tabItem {
Image.init(systemName: "star.fill")
Text("Home")
}
DemoView(screenName: "Result")
.tabItem {
Image.init(systemName: "star.fill")
Text("Result")
}
DemoView(screenName: "More")
.tabItem {
Image.init(systemName: "star.fill")
Text("More")
}
}
}
}
struct DemoView:View {
#State var text:String = ""
var screenName:String
var body: some View {
VStack{
Text(screenName)
.font(.title)
TextField("Buggy Keyboard Issue", text: $text)
.textFieldStyle(RoundedBorderTextFieldStyle())
Text("Issue : When keyboard appears, onAppear of other 2 tabs call automatically.")
.font(.footnote)
}
.padding()
.onAppear(perform: {
debugPrint("OnAppear of : \(screenName)")
})
}
}
This seems to be a bug of SwiftUI 2.0 but not sure.
Any help will be appreciated.
Thanks
Having the same issue myself, I think this is a bug or something like that, however I came up with a solution maybe a workaround until apple will fix it.
The thing that I did is basically I used a LazyVStack, and this seems to be working perfectly.
LazyVStack {
VStack{
Text(screenName)
.font(.title)
TextField("Buggy Keyboard Issue", text: $text)
.textFieldStyle(RoundedBorderTextFieldStyle())
Text("Issue : When keyboard appears, onAppear of other 2 tabs call automatically.")
.font(.footnote)
}
.padding()
.onAppear(perform: {
debugPrint("OnAppear of : \(screenName)")
})
}
Now the OnAppear method of other tab's view it is not called automatically when the keyboard appear.
Just implemented the following workaround:
struct ContentView: View {
var body: some View {
TabView(selection: $selectedTab) {
TabContentView(tag: 0, selectedTag: selectedTab) {
Text("Some tab content")
}
.tabItem {
Text("First tab")
}
TabContentView(tag: 0, selectedTag: selectedTab) {
Text("Another tab content")
}
.tabItem {
Text("Second tab")
}
}
}
#State private var selectedTab: Int = 0
}
private struct TabContentView<Content: View, Tag: Hashable>: View {
init(tag: Tag, selectedTag: Tag, #ViewBuilder content: #escaping () -> Content) {
self.tag = tag
self.selectedTag = selectedTag
self.content = content
}
var body: some View {
Group {
if tag == selectedTag {
content()
.frame(maxWidth: .infinity, maxHeight: .infinity)
} else {
Color.clear
}
}
.tag(tag)
}
private let tag: Tag
private let selectedTag: Tag
private let content: () -> Content
}
Not sure if it's stable enough but keyboard appearance doesn't trigger onAppear on tabs content anymore.
To avoid reloading your view try with on the TabView
.ignoresSafeArea(.keyboard, edges: .bottom)
It only works on iOS 14
Having an issue in SwiftUI where some Views are growing bigger vertically than the size of the device when using .edgesIgnoringSafeArea(.bottom). On an iPhone 11 Pro which is 812 pixels high I am seeing a view of size 846. I am using the Debug View Hierarchy to verify it. This has been tested on Xcode 11.4.1 and 11.1 and exists in both versions and probably all in between.
I have included sample code below.
I am pretty sure this is a SwiftUI bug, but was wondering if anyone has a workaround for it. I need the edgesIgnoringSafeArea(.bottom) code to draw the TabBar, and for the ProfileView() to extend to the bottom of the screen when I hide my custom tab bar.
struct ContentView: View {
var body: some View {
MainTabView()
}
}
struct MainTabView : View {
enum Item : CaseIterable {
case home
case resources
case profile
}
#State private var selected : Item = .home
var body: some View {
VStack(spacing: 0.0) {
ZStack {
HomeView()
.zIndex(selected == .home ? 1 : 0)
ResourcesView()
.zIndex(selected == .resources ? 1 : 0)
ProfileView()
.zIndex(selected == .profile ? 1 : 0)
}
// Code here for building and showing/hiding a Toolbar
// Basically just a HStack with a few buttons in it
}
.edgesIgnoringSafeArea(.bottom) // <- This causes the screen to jump to 846
}
}
struct ProfileView : View {
#State private var showQuestionnaireView = false
var body: some View {
NavigationView {
ZStack {
NavigationLink(destination: QuestionnaireView( showQuestionnaireView:$showQuestionnaireView),
isActive: $showQuestionnaireView) {
Text("Show Questionnaire View")
}
.navigationBarTitle("")
.navigationBarHidden(true)
}
}
}
}
struct QuestionnaireView : View {
#Binding var showQuestionnaireView : Bool
var body: some View {
GeometryReader { screenGeometry in
ZStack {
Color.orange
VStack {
Text("Top")
Spacer()
Text("Bottom")
}
}
}
}
}
HomeView() and ResourcesView() are just copies of ProfileView() that do their own thing.
When you run it you will see a button, push the button and a hidden Navigation Stack View pushes on the QuestionnaireView, this view contains a VStack with two text fields, neither of which you will be able to see due to this issue. Understandably the top one is behind the notch, but the bottom one is off the bottom of the screen. In my real project this issue is rarely seen at runtime, but switching between dark mode and light mode shows it. In the above code there is no need to switch appearances.
EDIT: FB7677794 for anyone interested, have not received any updates from Apple since lodging it 3 weeks ago.
EDIT2: Added some more code to MainTabBar
Update: This is fixed in Xcode 12 Beta 2
After reading the updated question I have made some changes and tried to make a small demo. In this, I am using the same approach as before, put NavigationView in your main tab view and with this you don't have to hide and show every time you come or leave your main tab view.
import SwiftUI
struct ContentView: View {
var body: some View {
MainTabView()
}
}
struct MainTabView : View {
enum Item : CaseIterable {
case home
case resources
case profile
}
#State private var selected : Item = .home
var body: some View {
NavigationView {
VStack(spacing: 0.0) {
ZStack {
Group {
HomeView()
.zIndex(selected == .home ? 1 : 0)
ResourcesView()
.zIndex(selected == .resources ? 1 : 0)
ProfileView()
.zIndex(selected == .profile ? 1 : 0)
}
.frame(minWidth: .zero, maxWidth: .infinity, minHeight: .zero, maxHeight: .infinity)
.background(Color.white)
}
HStack {
Group {
Image(systemName: "house.fill")
.onTapGesture {
self.selected = .home
}
Spacer()
Image(systemName: "plus.app.fill")
.onTapGesture {
self.selected = .resources
}
Spacer()
Image(systemName: "questionmark.square.fill")
.onTapGesture {
self.selected = .profile
}
}
.padding(.horizontal, 30)
}
.frame(height: 40)
.foregroundColor(Color.white)
.background(Color.gray)
// Code here for building and showing/hiding a Toolbar
// Basically just a HStack with a few buttons in it
}
.edgesIgnoringSafeArea(.bottom)
} // <- This causes the screen to jump to 846
}
}
struct ProfileView : View {
#State private var showQuestionnaireView = false
var body: some View {
// NavigationView {
ZStack {
NavigationLink(destination: QuestionnaireView( showQuestionnaireView:$showQuestionnaireView),
isActive: $showQuestionnaireView) {
Text("Show Questionnaire View")
}
.navigationBarTitle("")
.navigationBarHidden(true)
}
// }
}
}
struct QuestionnaireView : View {
#Binding var showQuestionnaireView : Bool
var body: some View {
GeometryReader { screenGeometry in
ZStack {
Color.orange
VStack {
Text("Top")
Spacer()
Text("Bottom")
}
}
.edgesIgnoringSafeArea(.bottom)
}
}
}
struct HomeView: View {
var body: some View {
NavigationLink(destination: SecondView()) {
Text("Home View")
}
}
}
struct ResourcesView: View {
var body: some View {
NavigationLink(destination: SecondView()) {
Text("Resources View")
}
}
}
struct SecondView: View {
var body: some View {
Text("Second view in navigation")
.background(Color.black)
.foregroundColor(.white)
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
.previewDevice(PreviewDevice(rawValue: "iPhone 11"))
}
}
It is due to undefined size for NavigationView. When you add your custom tab bar component, as in example below, that limits bottom area, the NavigationView will layout correctly.
Tested with Xcode 11.4 / iOS 13.4
struct MainTabView : View {
var body: some View {
VStack(spacing: 0.0) {
ZStack {
Color(.cyan)
ProfileView() // << this injects NavigationView
}
HStack { // custom tab bar
Button(action: {}) { Image(systemName: "1.circle").padding() }
Button(action: {}) { Image(systemName: "2.circle").padding() }
Button(action: {}) { Image(systemName: "3.circle").padding() }
}.padding(.bottom)
}
.edgesIgnoringSafeArea(.bottom) // works !!
}
}
I push an object that comes from the Core Data Database to a detail page with text fields.
When the user changes the text in the textfields and presses save the changes should be saved to the core data DB.
Problem: I have no clue how to modify/update/change an existing core data entity.
I probably need to get the original via a #FetchRequest but every time I try I get some problems.
Question 1: Let's say the entity has object.id as UUID, how can I
fetch that object in SwiftUI?
Question 2: How can I overwrite the fetched object with the changed
content of the textfields?
struct ProductDetail: View {
#State var barcode: Barcode
#Environment(\.presentationMode) var presentationMode
#Environment(\.managedObjectContext) var context
//let datahandler = Datahandler()
var body: some View {
VStack {
HStack {
Text("Barcode: ")
Spacer()
TextField("Barcode", text: Binding($barcode.code, ""))
.textFieldStyle(RoundedBorderTextFieldStyle())
.frame(width: 250, alignment: .trailing)
}
HStack {
Text("Amount: ")
Spacer()
TextField("Amount", text: Binding($barcode.amount, ""))
.textFieldStyle(RoundedBorderTextFieldStyle())
.frame(width: 250, alignment: .trailing)
.keyboardType(.numberPad)
}
HStack{
Button("Back") {
self.presentationMode.wrappedValue.dismiss()
}
Spacer()
Button("Save"){
//self.datahandler.updateBarcode(barcode: self.object)
self.editBarcode(barcode: barcode)
self.presentationMode.wrappedValue.dismiss()
}
}
.padding()
}
.padding()
}
func editBarcode(barcode: Barcode) {
// Question 1: Fetch Original object using barcode.id
// Question 2: How to but barcode into context so it can overwrite the core data original?
try? context.save()
}
Attempt:
func editBarcode(barcode: Barcode) {
#FetchRequest(sortDescriptors: [], predicate: NSPredicate(format: "self.id IN %#", barcode.id)) var results: FetchedResults<Barcode>
results.first?.amount = barcode.amount
results.first?.code = barcode.code
try? context.save()
}
Errors:
Argument type 'UUID?' does not conform to expected type 'CVarArg'
Cannot use instance member 'barcode' within property initializer;
property initializers run before 'self' is available
Property wrappers are not yet supported on local properties
I created a UIHostingController to put a SwiftUI view in my app. The root view contains a VStack, and inside that some HStacks and Pickers. The picker pushes the view wider than the screen by 8 points. It looks bad - text is slightly off screen. Usually I would fix this with auto layout constraints that pin the left and right edges of the view to the superview, with priority 1000 (required). What is the equivalent in SwiftUI?
Some boiled down code that triggers the problem
struct EditSegmentView: View {
#State var seconds: Int = 10
var body: some View {
VStack {
HStack {
Text("Pause after intro")
Spacer()
Text("10 seconds")
}
Picker(selection: $seconds, label: Text("Seconds")) {
ForEach(0..<60) { n in
Text("\(n) sec").tag(n)
}
}
}
}
}
That is displayed from a UIViewController, like this:
let editView = EditSegmentView()
let vc = UIHostingController(rootView: editView)
vc.modalPresentationStyle = .fullScreen
present(vc, animated: true)
I can't see anything obviously wrong with the code provided. You could try using a GeometryReader to force the VStack to be the width of the available space. Also add some padding(), because otherwise your Text will start right at the edge
var body: some View {
GeometryReader { proxy in
VStack {
//...
} .padding()
.frame(width: proxy.size.width)
}
}
Are there any clues as to why the view is expanding beyond the edges when you debug its view hierarchy?
This is a little late, but I had the same issue and fixed by making sure the outer VStack had it's alignment set to .leading. The inner Vstack also has alignment: leading and then added a .padding() to the inner VStack