Fixed View above a List View within a Navigation View - swift

How do I get a fixed (not scrolling) view above a List view within a NavigationView in SwiftUI?
The first picture shows what I try to achive / have so far. However the second picture shos what happens if I pull down, the title slides above the fixed content.
Using offset on the List and a ZStack I got close but the NavigationView Title keeps scrolling above the fixed viewwhen pulling down on the list.
Although I thought this is a more general question, to not risk this question gets closed, here is the code I have:
NavigationView {
ZStack{
List {
/* .. */
}
.listStyle(PlainListStyle())
.offset(x: 0, y: showFilter ? 100 : 0)
if showFilter {
VStack {
HStack {
Button(action: {
}, label: {
Text("Button")
})
.padding(5)
.background(Color(red: 238/255, green: 238/255, blue: 239/255))
.cornerRadius(5)
.padding()
Spacer()
}
Spacer()
}
}
}
.navigationBarItems(trailing:
Button(action: {
// initial
self.showFilter.toggle()
// completely different, odd behaviour (navigation title never fades away)
// withAnimation{ self.showFilter.toggle() }
}) {
Image(systemName: "line.horizontal.3.decrease.circle")
}
)
.navigationBarTitle("List")
}

Offset is weak approach here, because user will not see last rows in list when filter is shown and there are items more than screen area.
You can consider variant with section as placeholder (it is sticky and not overlapped by header) for filter (maybe with some custom style for buttons), so simple demo based on your code snapshot is like below.
Tested with Xcode 12 / iOS 14
NavigationView {
List {
Section(header: Group {
if showFilter {
VStack {
HStack {
Button(action: {
}, label: {
Text("Button")
})
.padding(5)
.background(Color(red: 238/255, green: 238/255, blue: 239/255))
.cornerRadius(5)
.padding()
Spacer()
}
Spacer()
}
}
}) {
ForEach(0..<20) { i in
Text("Item \(i)")
}
}}
.listStyle(PlainListStyle())

Not sure I understood your problem, can you please share a gif/video of the problem in action?
Either way you can try putting the list and the fixed view inside the same ZStack, put the list before the fixed view and give the fixed view a position attribute.
If the ZStack is inside the NavigationView it shouldn't affect the fixed view.
The Sudo-code would, roughly, be like this -
NavigationView {
ZStack {
List ...
FixedView()
.position(x: 100, y: 100)
}
}
If this was not enough you can check out Paul Hudson's tutorial on Absolute Positioning here.
And for more help please post your code and give us a video/gif demonstration so we can understand your problem and test it beforehand :)

Related

when I open the app on ipad simulator the design is broken

When I run the app on the iPad, the design does not appear on the screen. When you click on Home in the top left navigation bar, the design comes up, but it is half loaded. When I delete the NavigationView, the normal design appears but is not clickable.
struct MainView: View {
#EnvironmentObject var store: BlogPostsStore
#Environment(\.colorScheme) var colorScheme
var featuredPosts: [BlogPost] {
return store.blogPosts.filter {$0.featured == true}
}
var body: some View {
NavigationView {
ScrollView {
// featured article
if featuredPosts.count > 0 {
VStack {
HStack {
Text("Featured posts")
.font(.title.bold())
Spacer()
}
LazyVStack {
ForEach(featuredPosts) {post in
NavigationLink(destination: BlogPostView(blogPost: post)) {
BlogPostCardMain(blogPost: post)
}
}
}
}
.padding(.horizontal, 15)
.padding(.vertical, 30)
}
// latest articles
VStack {
HStack {
Text("Latest posts")
.font(.title.bold())
Spacer()
}
.padding(.horizontal, 15)
ScrollView(.horizontal, showsIndicators: false) {
LazyHStack(spacing: 15) {
if store.blogPosts.count >= 3 {
ForEach(store.blogPosts[0...2]) {post in
NavigationLink(destination: BlogPostView(blogPost: post)) {
BlogPostCardMain(blogPost: post)
}
}
} else {
ForEach(store.blogPosts[0..<store.blogPosts.count]) {post in
NavigationLink(destination: BlogPostView(blogPost: post)) {
BlogPostCardMain(blogPost: post)
}
}
}
}
.padding(.leading, 15)
.padding(.trailing, 30)
}
.frame(height: 420)
Spacer()
}
.padding(.bottom, 40)
}
.navigationBarTitle("Home")
.navigationBarItems(
trailing: Button(action: {store.refreshView()}) { Image(systemName: "arrow.clockwise.circle.fill")
.resizable()
.frame(width: 30, height: 30)
})
}
}
}
enter image description here
enter image description here
This is down to how NavigationView works on iPads (and also larger iPhones in landscape).
The first view given to NavigationView acts as the collapsible left hand navigation, which is a fixed width. Any NavigationLink destinations in that view will open in the main, “detail” view that takes up the full screen.
You can specify a second view underneath the first one to provide a ‘default’ view to display in the main screen:
NavigationView {
// the sidebar view
ScrollView {
// etc.
}
// the default view
Text("Default view")
}
You could also add a third view, which will automatically give your iPad a three-column view similar to that used by Mail, etc. if you wanted to.
Another option is to force the NavigationView to work exactly the same way as it does for an iPhone in portrait mode, by adding a .navigationViewStyle argument:
NavigationView {
// contents as before
}
.navigationViewStyle(.stack)
While that will give you an iPhone-like experience on the iPad, it doesn’t really take full use of the larger screen space without careful design work. For that reason, it’s usually a good idea to invest some time in coming up with an app design that is tailored to the default iPad style of navigation view.

SwiftUI View scrolls horizontal and vertical as well, instead of scrolling only vertically

I am building an app as a learning project. I ran into a problem, where one of my tabs scrolls vertically and horizontally for no reason.
There is no content outside the safe-area, and i can´t think of a reason for it to be scrolling sideways.
I already tried different things, replacing the GeometryReader with a VStack, specifying that i want it to be a vertical ScrollView. Adding .clipped to the end of the View as well as removing or replacing several other views
But nothing worked.
About the project:
The View is wrapped in a TabView and a NavigationView. All of the other tabs were wrapped the same way, and one also uses a ScrollView, yet does not have this problem.
The main Code:
struct InformationView: View {
#EnvironmentObject var model: ViewModel
var radialColor1 = Color.init(red: 225/255, green: 210/255, blue: 164/255)
var radialColor2 = Color.init(red: 233/255, green: 222/255, blue: 188/255)
var isShowingWarning = true
var body: some View {
GeometryReader { geo in
ScrollView(.vertical) {
VStack(alignment: .leading) {
Text("Here is a Text")
.padding(.vertical)
// W-fragen Stack
W_FragenCardView()
Text("Here is another Text")
.padding(.vertical)
Text("Kontakt:")
.font(.title3)
.bold()
ContactView()
}
.padding(.horizontal)
if isShowingWarning {
ZStack {
Rectangle()
.foregroundColor(Color.customRed())
Text("Here is a third Text")
.minimumScaleFactor(0.1)
.lineLimit(10)
.padding()
}
.frame(height: geo.size.height/5)
// .frame(height: 100)
}
Text("Current Theme: \(model.themes[1]!)")
}
}
.navigationBarTitle("Informationen", displayMode: .automatic)
}
}
This is were its wrapped:
struct ContentView: View {
#EnvironmentObject var model: ViewModel
var body: some View {
TabView {
NavigationView {
AnmeldungsView()
}
.tabItem {
Image(systemName: "square.and.pencil")
Text("Anmelden")
}
NavigationView {
FotosView()
}
.tabItem {
Image(systemName: "photo.fill.on.rectangle.fill")
Text("Fotos")
}
NavigationView {
InformationView()
}
.tabItem {
Image(systemName: "info.circle")
Text("Informationen")
}
}
}
}
And here an Image of how it looks when scrolling.
I hope this is sufficent to understand my problem. Any help or ideas are greatly appreciated.
P.S.: I´m new to stack overflow, so if a violated any rules please tell me :)
You should write an array([.horizontal, .vertical]) in ScrollView instead of .vertical.
change your code to this:
GeometryReader { geo in
ScrollView([.horizontal, .vertical]) {
VStack(alignment: .leading) {
Text("Here is a Text")
.padding(.vertical)
W_FragenCardView()
Text("Here is another Text")
.padding(.vertical)
Text("Kontakt:")
.font(.title3)
.bold()
ContactView()
}
.padding(.horizontal)
if isShowingWarning {
ZStack {
Rectangle()
.foregroundColor(Color.red)
Text("Here is a third Text")
.minimumScaleFactor(0.1)
.lineLimit(10)
.padding()
}
.frame(height: geo.size.height/5)
// .frame(height: 100)
}
}
And I hope it will works :)
It was a problem between geo.size.height/5 and the navigationBarTitle being on displayMode: .automatic.
This resulted in the available height changing and somehow bugged out. Either changing displayMode: to .inline or .large, or you replace the geo.size.height/5 w/ a hardcoded Int, i used 150.

SwiftUI: List, NavigationLink, and badges

I'm working on my first SwiftUI app, and in it would like to display a List of categories, with a badge indicating the number of items in that category. The title of the category would be on the left, and the badge would be right-aligned on the row. The list would consist of NavigationLinks so that tapping on one would drill further down into the view hierarchy. The code I've written to render the NavigationLinks looks like this:
List {
ForEach(myItems.indices) { categoryIndex in
let category = categories[categoryIndex]
let title = category.title
let fetchReq = FetchRequest<MyEntity>(entity: MyEntity(),
animation: .default)
NavigationLink(destination: MyItemView()) {
HStack(alignment: .center) {
Text(title)
Spacer()
ZStack {
Circle()
.foregroundColor(.gray)
Text("\(myItemsDict[category]?.count ?? 0)")
.foregroundColor(.white)
.font(Font.system(size: 12))
}
}
}
}
}
While it does render a functional NavigationLink, the badge is not displayed right-aligned, as I had hoped. Instead, it looks like this:
I know I'm getting hung up on something in my HStack, but am not sure what. How do I get it so that the category title Text takes up the majority of the row, with the badge right-aligned in the row?
SwiftUI doesn't know how big your Circle should be, so the Spacer doesn't do anything. You should set a fixed frame for it.
struct ContentView: View {
var body: some View {
List {
ForEach(0..<2) { categoryIndex in
let title = "Logins"
NavigationLink(destination: Text("Hi")) {
HStack(alignment: .center) {
Text(title)
Spacer()
ZStack {
Circle()
.foregroundColor(.gray)
.frame(width: 25, height: 25) // here!
Text("5")
.foregroundColor(.white)
.font(Font.system(size: 12))
}
}
}
}
}
}
}
Result:

SwiftUI: Two buttons with the same width/height

I have 2 buttons in an H/VStack. Both of them contain some text, in my example "Play" and "Pause". I would like to have that both buttons have the same width (and height) determined by the largest button. I have found some answers right here at SO but I can't get this code working unfortunately.
The following code illustrates the question:
import SwiftUI
struct ButtonsView: View {
var body: some View {
VStack {
Button(action: { print("PLAY tapped") }){
Text("Play")
}
Button(action: { print("PAUSE tapped") }) {
Text("Pause")
}
}
}
}
struct ButtonsView_Previews: PreviewProvider {
static var previews: some View {
ButtonsView()
}
}
The tvOS preview from Xcode shows the problem:
I would be thankful for an explanation for newbies 🙂
Here is run-time based approach without hard-coding. The idea is to detect max width of available buttons during drawing and apply it to other buttons on next update cycle (anyway it appears fluently and invisible for user).
Tested with Xcode 11.4 / tvOS 13.4
Required: Simulator or Device for testing, due to used run-time dispatched update
struct ButtonsView: View {
#State private var maxWidth: CGFloat = .zero
var body: some View {
VStack {
Button(action: { print("PLAY tapped") }){
Text("Play")
.background(rectReader($maxWidth))
.frame(minWidth: maxWidth)
}.id(maxWidth) // !! to rebuild button (tvOS specific)
Button(action: { print("PAUSE tapped") }) {
Text("Pause Long Demo")
.background(rectReader($maxWidth))
.frame(minWidth: maxWidth)
}.id(maxWidth) // !! to rebuild button (tvOS specific)
}
}
// helper reader of view intrinsic width
private func rectReader(_ binding: Binding<CGFloat>) -> some View {
return GeometryReader { gp -> Color in
DispatchQueue.main.async {
binding.wrappedValue = max(binding.wrappedValue, gp.frame(in: .local).width)
}
return Color.clear
}
}
}
You can implement the second custom layout example in the WWDC 2022 talk https://developer.apple.com/videos/play/wwdc2022/10056/ titled "Compose custom layouts with SwiftUI" which, if I understand the question, specifically solves it, for an arbitrary number of buttons/subviews. The example starts at the 7:50 mark.
after reading hit and trial implementing SO solns etc finally resolved this issue posting so that newbies as well as intermediate can benefit
paste it and obtain equal size(square) views
VStack(alignment: .center){
HStack(alignment:.center,spacing:0)
{
Button(action: {}, label: {
Text("Button one")
.padding(35)
.foregroundColor(.white)
.font(.system(size: 12))
.background(Color.green)
.frame(maxWidth:.infinity,maxHeight: .infinity)
.multilineTextAlignment(.center)
.cornerRadius(6)
}).background(Color.green)
.cornerRadius(6)
.padding()
Button(action: {}, label: {
Text("Button two")
.padding(35)
.foregroundColor(.white)
.font(.system(size: 12))
.frame(maxWidth:.infinity,maxHeight: .infinity)
.background(Color.green)
.multilineTextAlignment(.center)
}) .background(Color.green)
.buttonBorderShape(.roundedRectangle(radius: 8))
.cornerRadius(6)
.padding()
}.fixedSize(horizontal: false, vertical: true)
Add as many as buttons inside it. You can adjust it for VStack by adding only one button in hstack and add another button in another Hstack. I gave a general soln for both VStack and Hstack. You can also adjust padding of button as .padding(.leading,5) .padding(.top,5) .padding(.bottom,5) .padding(.trailing,5) to adjust the gaps between buttons
I think the best solution is to use GeometryReader, which resizes the width of the content of the Button. However, you need to check that you set a width of the Wrapper around the GeometryReader, because otherwise it would try to use the full screen width. (depends where you use that view, or if it is your primary view)
VStack
{
GeometryReader { geo in
VStack
{
Button(action: { print("PLAY tapped") }){
Text("Play")
.frame(width: geo.size.width)
}
.border(Color.blue)
Button(action: { print("Pause tapped") }){
Text("PAUSE")
.frame(width: geo.size.width)
}
.border(Color.blue)
}
}
}
.frame(width: 100)
.border(Color.yellow)
... which will look like that.
What happens if you put a Spacer() right after the Text("Play")? I think that might stretch out the 'Play' button.
Or maybe before and after Text("Play").

SwiftUI List/Form/ScrollView being clipped when offset

I changed the y offset of a list, and now it's being clipped.
I am trying to make it so that when you scroll, you can partially see the text underneath the Title and buttons at the top of the view. In other words, I want the top section of the screen to be slightly transparent.
I added the offset to the list, so that it didn't overlap with the information at the top.
The image above is with the VStack in my code showing. I thought that the VStack might be getting in the way, so I commented it out and the image below was the result:
Here's my code:
var body: some View {
ZStack {
VStack {
HStack {
Button(action: {self.showAccountView.toggle()}) {
Image(systemName: "person.fill")
.renderingMode(.original)
.font(.system(size: 20, weight: .bold))
.frame(width: 44, height: 44)
.modifier(NavButtons())
}
.sheet(isPresented: $showAccountView) {
AccountView()
}
Spacer()
Button(action: { self.showHelpCenter.toggle()}) {
Image(systemName: "questionmark")
.renderingMode(.original)
.font(.system(size: 20, weight: .bold))
.modifier(NavButtons())
}
.sheet(isPresented: $showHelpCenter) {
HelpCenter()
}
}
.frame(maxWidth: .infinity)
.padding(.horizontal)
Spacer()
}
List {
ForEach (store.allLogs) { thing in
VStack (alignment: .leading) {
HStack {
Text("\(thing.date) , \(thing.time)")
}
Text(thing.notes)
.multilineTextAlignment(.leading)
}
}
}.offset(y: 50)
}
}
EDIT:
This is one possible solution:
struct MyList: UIViewRepresentable {
func makeUIView(context: Context) -> UITableView {
let view = UITableView()
view.clipsToBounds = false
return view
}
func updateUIView(_ uiView: UITableView, context: Context) {
}
}
and then you would use makeUIView and updateUIView to update the cells. This is just messy and it's not really using SwiftUI at that point.
Second Edit
I've found this issue with a scrollView as well as a form:
The black is the background. Here's the code for all three:
Group {
List ((0 ... 10), id: \.self) {
Text("Row \($0)")
}
.offset(y: 200)
.border(Color.blue, width: 3)
.background(Color.black)
ScrollView {
Text("Text")
}
.foregroundColor(.white)
.offset(y: 200)
.border(Color.blue, width: 3)
.background(Color.black)
Form {
Text("Text")
}
.offset(y: 200)
.border(Color.blue, width: 3)
.background(Color.black)
}
Here are the wireframes of a List:
Here are the names of frames that are the same height as the List/Form/ScrollView:
List:
PlainList.BodyContent
ListCore.Container
ListRepresentable
View Host
TableWrapper
UpdateCoalescingTableView
Form:
GroupList.BodyContent
ListCore.Container
ListRepresentable
View Host
TableWrapper
UpdateCoalescingTableView
ScrollView:
ScrollViewBody
SystemScrollView
View Host
HostingScrollView
I guess that my question has changed from "how do I do this..." to "Why is this happening?"
I'm pretty confused about what exactly is going on.
UIScrollView.appearance().clipsToBounds = false
Stick this in the body of AppDelegate.swift -> applicationDidFinishLaunchingWithOptions(). It will make all scroll views across your application unclipped by default.
The problem is that a list has a table view (which is a scroll view) underneath. Scroll views by default are clipped. We just need to change that default.
Using LazyHStack instead of HStack solves the clipping problem.