SwiftUI ScrollView dynamic Height - swift

I currently have the problem that my scroll view does not automatically adjust to the height of the content. I think ASCollectionView is the one that causes the most problems at the moment. If I adjust the frame height manually (here e.g. 200 - see picture), then everything is visible. Of course I want to have it automatically. Do you have any idea how this could work?
I would be very happy! Have a nice afternoon.
Florian
ScrollView {
ForEach(self.collectionListViewModel.collections) { collection in
NavigationLink(destination: GameListView()) {
VStack(alignment: .leading, spacing: 5) {
// Name der Sammlung:
Text(collection.name)
.font(.headline)
.foregroundColor(Color.black)
// Optional: Für welche Konsolen bzw. Plattformen:
ASCollectionView(data: collection.platforms, dataID: \.self) { item, _ in
Text("\(item)")
.fixedSize()
.padding(.horizontal, 10)
.padding(.vertical, 5)
.background(Color(.blue))
.foregroundColor(Color.white)
.cornerRadius(5)
}
.layout {
let fl = AlignedFlowLayout()
fl.estimatedItemSize = UICollectionViewFlowLayout.automaticSize
return fl
}
}
.padding(.vertical, 10)
}
Divider()
}
.padding(.horizontal, 20)
}

The problem here is that ASCollectionView will (by default) take up however much room it is given. When nested in a ScrollView the amount of space available is undefined. You could set up the collectionView to automatically size itself to its content. An example is given in the demo project here
However, nesting scrollviews can cause difficulty, so I think the behaviour you're after would be more easily achieved by placing the title text as a section in the ASCollectionView.

Related

How to keep an item exactly in the center of an HStack with other items in the HStack

I have two items in an HStack. I want one of them to be perfectly horizontally centered and the other to be on the left side (have an alignment of .leading). Here is my code so far:
HStack {
Text("1")
.frame(alignment: .leading)
Text("Hello")
.frame(alignment: .center)
}
I have figured out that the .overlay method works. However, I still want Text("Hello") to be aware of the position of Text("1") so that if Text("Hello") were a longer string, it would know not to cover up Text("1"), which is exactly what happens when the .overlay function is used. So I'm wondering if there are any other solutions.
You can add one more Text view inside HStack as hidden which have the same content as your left align text view along with Spacer. Something like this.
HStack {
Text("1")
Spacer()
Text("Good Morning, Hello World. This is long message")
.multilineTextAlignment(.center)
Spacer()
Text("1") // Set the same content as left aligned text view
.hidden()
}
.padding(.horizontal)
Preview
Not sure if it's the best way, but you can use a GeometryReader to get the screen width and skip the first half.
To place the text exactly in the center, you should also calculate the width of the text. you can use a simple extension to get the size of your text:
extension String {
public func size(withFont font: UIFont) -> CGSize {
(self as NSString).size(withAttributes: [.font: font])
}
}
Here is the example code:
GeometryReader { geo in
HStack {
Spacer()
.frame(width: (geo.size.width - "center".size(withFont: UIFont.systemFont(ofSize: 18)).width) / 2)
HStack {
Text("center")
Text("another")
}
}
}

How do i set full height on a component in a scroll view?

I am a web developer trying to learn SwiftUI and have some questions regarding about my issue.
So my code architecture is as follows:
ContentView.swift
ScrollView(.vertical){
Foreach(users){
user in UserCard()
}
}
UserCard.swift
VStack(){
Image()
Text("long text")
}
How do I make my UserCard to always the height of my iphone? As of right now, my UserCard sometimes overflow or cuts off too quickly depending on my Text length.
Any help would be appreciated. And any tips for a SwiftUI beginner would be great!
Thank you in advanced.
If you want it to be full screen and ignoring safe area you can set the UserCard's height to screen height like this:
VStack(){
Image()
Text("long text")
}
.frame(height: UIScreen.main.bounds.height)
And if you want it to respect safe area you could use a GeometryReader:
GeometryReader { geometry in
ScrollView {
Color.red
.frame(height: geometry.size.height)
Color.blue
.frame(height: geometry.size.height)
Color.yellow
.frame(height: geometry.size.height)
}
}
one Question -> one Answer
for second part of your Question need new Question with own Topic.
ZStack()
{
VStack()
{
Image(systemName: "star")
Text("long text")
}
}
.ignoresSafeArea()

Fixed View above a List View within a Navigation View

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 :)

SwiftUI Nested GeometryReader - Breaking UI

So I am trying to understand why my subview (TopView) is having weird resizing issues.
Here is the sample
import SwiftUI
struct ContentView: View {
#State var isInterfaceHidden: Bool = false
var body: some View {
VStack(spacing: 0, content: {
if !isInterfaceHidden {
TopView()
.background(Color.yellow)
}
Rectangle()
.foregroundColor(Color.red)
/// We make sure it won't cover the top and bottom view.
.zIndex(-1)
if !isInterfaceHidden {
Rectangle()
.foregroundColor(Color.yellow)
.frame(height: 80)
}
})
.navigationBarTitle("")
.navigationBarHidden(true)
}
}
struct TopView: View {var body: some View {
HStack(content: {
VStack(spacing: 0, content: {
Text("Text to show, it is a title.")
.tracking(0.2)
.foregroundColor(.white)
.lineLimit(1)
GeometryReader(content: { geometry in
Text("Text to show, it is a subline.")
.tracking(0.2)
.foregroundColor(.white)
.lineLimit(1)
})
.background(Color.purple)
})
})
.padding([.leading, .trailing], 20)
}
}
I tried to set a .fixedSize() like this:
GeometryReader(content: { geometry in
Text("Text to show, it is a subline.")
.tracking(0.2)
.foregroundColor(.white)
.lineLimit(1)
})
.background(Color.purple)
But it is not fitting the text exactly, so I am not sure if this is the right solution. Do you guys have any idea?
Be aware that GeometryReader has had what appears to be a regression as of 14.0 (26/Sep/20) - or perhaps a wonderfully undocumented change of behaviour - with weighting layouts towards the topleft corner - rather than the center.
This has only appeared with apps I developed and built with XCode 12 - an XCode-11-compiled-app running on iOS 14 did not exhibit the issue. Most tutorials on the net will be assuming this worked the way it did in iOS 13/XCode 11 and your code may function differently
iOS 14 has Changed (or broken?) SwiftUI GeometryReader for a more involved question with the same issues
As far as I know, GeometryReader passes back its parent a size that is given by the parent unless you set frame() to GeometryReader explicitly. Even so, If you want to fit the area of GeometryReader to the Text view (exactly your custom view), you will have to calculate a height of the custom view by using preference or anchorPreference and then set it as a height of GeometryReader in order to let the parent know what size it needs to assign.
I hope the following link will be helpful.
https://swiftui-lab.com/communicating-with-the-view-tree-part-1/
GeometryReader fit to View
If you're looking for the GeometryReader to not affect the size of your view, you should make an inversion. The view that you return inside the GeometryReader should be out, and the GeometryReader itself should be put in a background or in a overlay of that View.
Text("Text to show, it is a subline.")
.tracking(0.2)
.foregroundColor(.white)
.lineLimit(1)
.overlay(
GeometryReader(content: { geometry -> Color in
print(geometry.frame(in: .global))
return Color.clear
})
)
.background(Color.purple)
Either way (background or overlay), would solve your problem. Try changing overlay to background to see.
Just remember to return a Color.clear, this way, the GeometryReader becomes invisible and it doesn't change the View.

SwiftUI ScrollView only scroll in one direction

Trying to make a custom list using a view as the list row style (to get rid of the ugly line separates in the list by default).
However, once I put my ZStack rows inside a scroll view, the scroll view scrolls in both directions and not just vertically.
Here is the contentView:
NavigationView {
ScrollView{
VStack(alignment: .leading){
ForEach(friends) { friends in
NavigationButton(destination: MessageDetailView(friend: friends)) {
CustomListItem(friend: friends)
}
}
Spacer()
}
}
.foregroundColor(.black)
.navigationBarTitle(Text("Messages"))
}
and here is the customListItem:
Group{
ZStack {
RoundedRectangle(cornerRadius: 10)
.shadow(radius: 1, y:1)
.frame(width: UIScreen.main.bounds.width - 32, height: 75)
.foregroundColor(.gray)
HStack {
Image(systemName: "person.crop.circle")
.resizable()
.frame(width: 50, height: 50)
VStack(alignment: .leading) {
HStack {
Text(friend.name)
.font(.headline)
Text("\(friend.date, formatter: dateFormatter)")
}
Text(friend.messagesReceived[0])
.font(.subheadline)
} .lineLimit(nil)
Spacer()
Image(systemName: "chevron.right.circle")
.foregroundColor(.black)
}.padding(10)
}.padding([.leading, .trailing])
}
Is there any way I can limit the scrolling to vertical or force a frame on this?
Trying to use the .frame(...) modifier does not work as I've tried it. This results in the view not loading at all.
Example Images:
As of Xcode 11 beta 3 (11M362v) there is an axes parameter when constructing a ScrollView which can be set to .horizontal or .vertical
ScrollView(.vertical, showsIndicators: true) { ... }
This can be achieved with a GeometryReader. Wrap your ScrollView in one and then set the width of your VStack.
GeometryReader is a super easy, and pretty useful trick to have in your belt for SwiftUI :)
Here's how I got it working with your code:
NavigationView {
GeometryReader { geometry in
ScrollView {
VStack(alignment: .leading){
ForEach(self.friends) { friend in
NavigationButton(destination: MessageDetailView(friend: friend)) {
CustomListItem(friend: friend)
}
}
Spacer()
}.frame(width: geometry.size.width)
}
.foregroundColor(.black)
.navigationBarTitle(Text("Messages"))
}
}
Using iOS 14, Swift 5, SwiftUI 2.0
This is the answer... you can use both vertical and horizontal in a set.
ScrollView([.vertical,.horizontal]) {
....
}
Although in most cases the above answers works but none of them did work for me!
In my case, the problem was the scrollView children which were wider than the scrollView!
adding a static width to them did the trick.
Try being more specific with the scroll direction like this
ScrollView(.vertical) {}