Using XCode12, beta 4, I am trying to separate a header area and a ScrollView. I've tried both VStack and GeometryReader; however, when I click in the header area, the navigation link in the ScrollView item beneath it is triggered.
If I use a list, this undesired behavior is not observed. If I use the ScrollView in XCode 11.6 and build for iOS13, this undesired behavior is also not observed.
Did something change with ScrollViews in VStacks or GeometryReaders in iOS14? Why does the scrollview "slide" under the object in the VStack or GeometryReader?
struct ContentView: View {
var body: some View {
NavigationView{
//GeometryReader { geo in
VStack{
VStack{
Text("Blah")
}//.frame(height: geo.size.height*0.20)
VStack {
ScrollView {
ForEach(0..<10) { i in
NavigationLink(destination: Text("\(i)")) {
cardRow(i: i)
.frame(maxWidth: .infinity)
.foregroundColor(Color(UIColor.label))
.padding()
.background(RoundedRectangle(cornerRadius: 12))
.foregroundColor(Color(UIColor.secondarySystemFill))
.padding()
}
}
}
}//.frame(height: geo.size.height*0.90)
}
}.navigationViewStyle(StackNavigationViewStyle())
}
struct cardRow: View {
var i: Int
var body: some View {
HStack {
VStack {
Text("88")
}.hidden()
.overlay(
VStack{
Text("\(i)")
}
)
Divider()
.background(Color(UIColor.label))
Spacer()
}
}
}
}
Here is worked solution (tested with Xcode 12 / iOS 14)
VStack {
ScrollView {
ForEach(0..<10) { i in
NavigationLink(destination: Text("\(i)")) {
cardRow(i: i)
.frame(maxWidth: .infinity)
.foregroundColor(Color(UIColor.label))
.padding()
.background(RoundedRectangle(cornerRadius: 12))
.foregroundColor(Color(UIColor.secondarySystemFill))
.padding()
}
}
}.clipped().contentShape(Rectangle()) // << here !!
}
Related
I have an issue with Views in the sample app I am trying to help me learn SwiftUI views.
The black bars at the top and bottom shouldn't be there. I want to extend the view to the top and bottom of the safe area limit.
Here is the code using Xcode 13.4.1 and iOS 15.5:
import SwiftUI
struct ContentView: View {
var body: some View {
TabView {
HotelView()
.tabItem {
Image(systemName: "house")
.resizable()
Text("Home")
}
HomeView(title: "My Trips")
.tabItem {
Image(systemName: "suitcase")
Text("My Trips")
}
HomeView(title: "Saved")
.tabItem {
Image(systemName: "heart")
Text("Saved")
}
HomeView(title: "Profile")
.tabItem {
Image(systemName: "person")
Text("Profile")
}
}
.frame(maxWidth: .infinity, maxHeight: .infinity)
}
}
Also the HotelView file:
import SwiftUI
struct HotelView: View {
var body: some View {
NavigationView {
List(hotels) { hotel in
ScrollView(.vertical, showsIndicators: true) {
NavigationLink(destination: DetailView(hotels: hotel)) {
HStack {
Image(hotel.image)
.resizable()
.frame(height: 100)
.frame(maxWidth: .infinity)
.clipShape(RoundedRectangle(cornerRadius: 10, style: .continuous))
.overlay(
Text(hotel.city)
.fontWeight(.bold)
.foregroundColor(.white),alignment: .center)
}
}
}
}
// navtitle
}
}
}
And the result in the simulator is:
Black bars on top and bottom
import SwiftUI
#main
struct Mohsin_city_plannerApp: App {
var body: some Scene {
WindowGroup {
ContentView()
}
}
}
So the solution is to add launch screen to your plist info file even in SwiftUI
is anybody able to see why I am unable to navigate to a new view I have created called "FavouritesView" using NavigationLink?
this is the code of the HomeView where I am navigating from
var body: some View {
VStack(spacing: 0) {
Text("test")
.font(.title.bold())
.frame(maxWidth: .infinity)
.overlay(alignment: .trailing) {
NavigationLink {
FavouritesView()
} label: {
Image(systemName: "doc.badge.gearshape")
.font(.title3)
.foregroundColor(.white)
.padding(.trailing, 20)
}
}
.padding(.bottom, 10)
all I'm doing is putting in a navigation link at the top right next to the title
. just want to tap and goto my other view. Probably something im missing here anyone know the fix ?
thanks
Wrap your VStack in a NavigationView like so:
var body: some View {
NavigationView {
VStack(spacing: 0) {
Text("test")
.font(.title.bold())
.frame(maxWidth: .infinity)
.overlay(alignment: .trailing) {
NavigationLink {
FavouritesView()
} label: {
Image(systemName: "doc.badge.gearshape")
.font(.title3)
.foregroundColor(.white)
.padding(.trailing, 20)
}
}
.padding(.bottom, 10)
And if you're using Xcode 14 beta, use the new NavigationStack for a more Advanced implementation. See this.
I'm struggling with a view where I want to have multiple pickers embedded in
other views. When I wrap the pickers in a Form, I get the desired behavior for the
picker but there is a lot of extra space around the pickers that I can't seem to
automatically adjust.
This is an example - the space in the red outline seems to be determined by the other
view elements not the size of the picker.
I can, of course, hard-code a frame height for the Form but that is trial and error
and would only be specific to the device and orientation. I have tried multiple
versions of Stacks inside Stacks with padding, GeometryReader etc, but I have not come up with any
solution. As an aside, I DO want the picker labels, otherwise I could just remove
the Form.
I also tried setting UITableView.appearance().tableFooterView in an init() but that did not work either.
Here is a simplified version:
struct ContentView4: View {
#State var selectedNumber1: Int = 1
#State var selectedNumber2: Int = 2
#State var selectedNumber3: Int = 3
var body: some View {
NavigationView {
VStack(alignment: .leading) {
HStack {
Spacer()
Text("Compare up to 3")
.font(.caption)
Spacer()
}//h
Form {//for pickers
Picker(selection: $selectedNumber1, label: Text("A")) {
ForEach(0..<10) {
Text("\($0)")
}
}//picker
Picker(selection: $selectedNumber2, label: Text("B")) {
ForEach(0..<10) {
Text("\($0)")
}
}//picker
Picker(selection: $selectedNumber3, label: Text("C")) {
ForEach(0..<10) {
Text("\($0)")
}
}//picker
}//form for pickers
.padding(.horizontal, 10)
//.frame(height: 200) //don't want to hard code this
VStack(alignment: .leading) {
HStack {
Text("A")
.frame(width: 100)
Text("B")
.frame(width: 100)
Text("C")
.frame(width: 100)
}
.padding(.horizontal, 10)
ScrollView(.vertical, showsIndicators: false) {
VStack(alignment: .leading){
Text("A title line")
.font(.headline)
.padding(.vertical, 5)
HStack {
Text("Number")
.frame(width: 100)
Text("Number")
.frame(width: 100)
Text("Number")
.frame(width: 100)
}
Text("Another title line")
.font(.headline)
.padding(.vertical, 5)
HStack {
Text("Something")
.frame(width: 100)
Text("Something")
.frame(width: 100)
Text("Something")
.frame(width: 100)
}
Text("A Third title line")
.font(.headline)
.padding(.vertical, 5)
HStack {
Text("More")
.frame(width: 100)
Text("More")
.frame(width: 100)
Text("More")
.frame(width: 100)
}
}
}//scroll
.padding(.horizontal, 10)
}
.navigationBarTitle("Compare Three", displayMode: .inline)
}
}//nav
}//body
}//struct
Interestingly, I am able to get a solution by removing the form and wrapping each
picker in a menu, like this:
Menu {
Picker(selection: $selectedNumber2, label: EmptyView()) {
ForEach(0..<10) {
Text("\($0)")
}
}//picker
} label: {
HStack {
Text("B")
Spacer()
Image(systemName: "chevron.right")
.resizable()
.frame(width: 14, height: 14)
}//h
}//menu label
However, I still like the look of the Form better if I could automatically configure
the space around the Form items.
Any guidance would be appreciated. Xcode 13.4, iOS 15.5
Form (and List) is not meant to be stacked inside other views like this, which is why it has such strange behavior.
Thankfully, it's fairly simple to recreate the stuff you do want using NavigationLink. Here’s a quick example of a couple custom views that do just that:
// drop-in NavigationLink replacement for Picker
struct NavigationButton<Content: View, SelectionValue: Hashable> : View {
#Binding var selection: SelectionValue
#ViewBuilder let content: () -> Content
#ViewBuilder let label: () -> Text
var body: some View {
NavigationLink {
PickerView(selection: $selection, content: content, label: label)
} label: {
HStack {
label()
Spacer()
Text(String(describing: selection))
.foregroundColor(.secondary)
}
.contentShape(Rectangle())
}
.buttonStyle(NavigationLinkButtonStyle())
}
}
// subview for the Picker page, which lets us use `dismiss()`
// to pop the subview when the user selects an option
struct PickerView<Content: View, SelectionValue: Hashable> : View {
#Binding var selection: SelectionValue
#ViewBuilder let content: () -> Content
#ViewBuilder let label: () -> Text
#Environment(\.dismiss) private var dismiss
var body: some View {
Form {
Picker(selection: $selection, content: content, label: label)
.pickerStyle(.inline)
.labelsHidden()
.onChange(of: selection) { _ in
dismiss()
}
}
.navigationTitle(label())
}
}
// recreate the appearance of a List row
struct NavigationLinkButtonStyle: ButtonStyle {
func makeBody(configuration: Configuration) -> some View {
HStack {
configuration.label
.frame(maxWidth: .infinity)
Image(systemName: "chevron.right")
.font(.footnote.bold())
.foregroundColor(Color(UIColor.tertiaryLabel))
}
.padding()
.background(
Rectangle()
.fill(configuration.isPressed ? Color(UIColor.quaternaryLabel) : Color(UIColor.systemBackground))
)
}
}
If you like the .insetGrouped style you got using Form, we can replicate that by putting NavigationButton inside a clipped VStack:
VStack(spacing: 0) {
NavigationButton(selection: $selectedNumber1) {
ForEach(0..<10) {
Text("\($0)")
}
} label: {
Text("A")
}
Divider()
NavigationButton(selection: $selectedNumber2) {
ForEach(0..<10) {
Text("\($0)")
}
} label: {
Text("B")
}
}
.clipShape(RoundedRectangle(cornerRadius: 11))
.padding()
.background(Color(UIColor.systemGroupedBackground))
And here’s a screenshot showing my custom views above your original Form.
(And if you like Picker as a popup menu, you could use Menu instead of NavigationLink)
I have following problem. I want to create a vertical ScrollView with many rows. At the bottom of the view I have an info bar which appears over the scroll view because I put all the items in a ZStack. Here is my code and what it produces:
struct ProblemView: View {
var body: some View {
ZStack {
ScrollView(.vertical, showsIndicators: true) {
VStack {
ForEach(0..<100, id:\.self) {i in
HStack {
Text("Text \(i)")
.foregroundColor(.red)
Spacer()
Image(systemName: "plus")
.foregroundColor(.blue)
}
.frame(maxWidth: .infinity, alignment: .leading)
.padding()
Divider()
}
}
}
VStack {
Spacer()
HStack {
Text("Some Info here")
Image(systemName: "info.circle")
.foregroundColor(.blue)
}
.padding()
.frame(maxWidth: .infinity)
.ignoresSafeArea()
.background(.ultraThinMaterial)
}
}
}
}
struct ProblemView_Previews: PreviewProvider {
static var previews: some View {
ProblemView()
}
}
As you can see the drag indicator is hidden behind the info frame. Also the last item can't be seen because it is also behind the other frame. What
I want is that the drag indicator stops at this info frame. Why am I using a ZStack and not just a VStack? I want that this opacity effect behind the info frame, you get when you scroll.
A edit on my preview post has been added and therefore I cannot edit it... I am just gonna post the answer as an other one then.
This is the code that fixes your problem:
import SwiftUI
struct ProblemView: View {
var body: some View {
ScrollView {
VStack {
ForEach(0..<100, id:\.self) {i in
HStack {
Text("Text \(i)")
.foregroundColor(.red)
Spacer()
Image(systemName: "plus")
.foregroundColor(.blue)
}
.frame(maxWidth: .infinity, alignment: .leading)
.padding()
Divider()
}
}
.frame(maxWidth: .infinity)
}
.safeAreaInset(edge: .bottom) { // 👈🏻
VStack {
HStack {
Text("Some Info here")
Image(systemName: "info.circle")
.foregroundColor(.blue)
}
.padding()
.frame(maxWidth: .infinity)
.ignoresSafeArea()
.background(.ultraThinMaterial)
}
}
}
}
struct ProblemView_Previews: PreviewProvider {
static var previews: some View {
ProblemView()
}
}
We cannot control offset of indicator, but we can make all needed views visible by injecting last empty view with the same height (calculated dynamically) as info panel.
Here is possible approach. Tested with Xcode 13.2 / iOS 15.2
struct ProblemView: View {
#State private var viewHeight = CGFloat.zero
var body: some View {
ZStack {
ScrollView(.vertical, showsIndicators: true) {
VStack {
ForEach(0..<100, id:\.self) {i in
HStack {
Text("Text \(i)")
.foregroundColor(.red)
Spacer()
Image(systemName: "plus")
.foregroundColor(.blue)
}
.frame(maxWidth: .infinity, alignment: .leading)
.padding()
Divider()
}
Color.clear
.frame(minHeight: viewHeight) // << here !!
}
}
VStack {
Spacer()
HStack {
Text("Some Info here")
Image(systemName: "info.circle")
.foregroundColor(.blue)
}
.padding()
.frame(maxWidth: .infinity)
.ignoresSafeArea()
.background(.ultraThinMaterial)
.background(GeometryReader {
Color.clear.preference(key: ViewHeightKey.self,
value: $0.frame(in: .local).size.height)
})
}
}
.onPreferenceChange(ViewHeightKey.self) {
self.viewHeight = $0
}
}
}
struct ViewHeightKey: PreferenceKey {
static var defaultValue: CGFloat { 0 }
static func reduce(value: inout CGFloat, nextValue: () -> CGFloat) {
value = value + nextValue()
}
}
I am trying to change background color main this view but unable to do it. I tried to put background(Color.green) at HStack, VSTack and even on ZStack but it did not work, not sure if i am putting at right place. By default it is taking phone or simulator color which is white but i want to apply custom background color
My Xcode version is 11.5
struct HomePageView: View {
#State var size = UIScreen.main.bounds.width / 1.6
var body: some View {
GeometryReader{_ in
VStack {
HStack {
ZStack{
// main home page components here....
NavigationView{
VStack {
AssignmentDaysView()
}.background(Color.lairBackgroundGray)
.frame(width: 100, height: 100, alignment: .top)
.navigationBarItems(leading: Button(action: {
self.size = 10
}, label: {
Image("menu")
.resizable()
.frame(width: 30, height: 30)
}).foregroundColor(.appHeadingColor), trailing:
Button(action: {
print("profile is pressed")
}) {
HStack {
NavigationLink(destination: ProfileView()) {
LinearGradient.lairHorizontalDark
.frame(width: 30, height: 30)
.mask(
Image(systemName: "person.crop.circle")
.resizable()
.scaledToFit()
)
}
}
}
).navigationBarTitle("Home", displayMode: .inline)
}
HStack{
menu(size: self.$size)
.cornerRadius(20)
.padding(.leading, -self.size)
.offset(x: -self.size)
Spacer()
}
Spacer()
}.animation(.spring()).background(Color.lairBackgroundGray)
Spacer()
}
}
}
}
}
struct HomePageView_Previews: PreviewProvider {
static var previews: some View {
HomePageView()
}
}
In your NavigationView you have a VStack. Instead you can use a ZStack and add a background below your VStack.
Try the following:
NavigationView {
ZStack {
Color.green // <- or any other Color/Gradient/View you want
.edgesIgnoringSafeArea(.all) // <- optionally, if you want to cover the whole screen
VStack {
Text("assignments")
}
.background(Color.gray)
.frame(width: 100, height: 100, alignment: .top)
}
}
Note: you use many stacks wrapped in a GeometryReader which you don't use. Consider simplifying your View by removing unnecessary stacks. Also you may not need a GeometryReader if you use UIScreen.main.bounds (however, GeometryReader is preferred in SwiftUI).
Try removing some layers: you can start with removing the top ones: GeometryReader, VStack, HStack...
Try the following:
Change the view background color especially safe area also
struct SignUpView: View {
var body: some View {
ZStack {
Color.blue //background color
}.edgesIgnoringSafeArea(.all)