I have centered my Scroll View by using GeometryReader like this:
struct ContentView: View {
var body: some View {
NavigationView {
GeometryReader { geom in
ScrollView(.vertical, showsIndicators: false) {
ZStack {
my_super_view()
}
.navigationBarTitle("Main")
.frame(width: geom.size.width)
.frame(minHeight: geom.size.height)
}
}
}.navigationViewStyle(StackNavigationViewStyle())
}
}
And it works almost great. My problem is that the height of container ZStack is too big and the page could be scrolled down like this:
And when i try to do smth like:
.frame(minHeight: geom.size.height-52)
The page stops scrolling, but is poorly centered. How can I properly center the page so that it fits entirely on the main screen without scrolling below it?
UPD:
I found out that i could use position() instead of frame(). In that case page centred properly, but it may cause some issues in future.
struct ContentView: View {
var body: some View {
NavigationView {
GeometryReader { geom in
ScrollView(.vertical, showsIndicators: false) {
ZStack {
my_super_view()
}
.navigationBarTitle("Main")
.position(x: geom.size.width/2, y: geom.size.height/2)
}
}
.navigationBarTitle("Диа Компаньон")
.navigationBarTitleDisplayMode(.inline)
}.navigationViewStyle(StackNavigationViewStyle())
}
}
I had to use .inline NavigationBarDisplayMode and position instead of setting up width and height of ZStack
Related
I'm trying to setup an interface like this with a content view on top and a scroll view at the bottom. When you scroll up in scroll view, I want the scroll view's frame to move up until it's fullscreen. Similarly, once the scroll view is fullscreen I want it to come back down when you scroll in the reverse direction. This is similar to the modal sheet behavior with detents, but I want both views to be interactive at the same time. Is this possible?
Here's an example:
struct ContentView: View {
let content = 1...20
var body: some View {
VStack {
VStack {
Spacer()
HStack {
Spacer()
Text("Content View")
Spacer()
}
Spacer()
}
.background(Color(uiColor: .systemGray5))
List {
ForEach(content, id: \.self) { item in
Text("\(item)")
}
}
.frame(height: 300)
.listStyle(.plain)
}
}
}
I want the frame to change as you scroll up.
I've been looking into this after solving this issue on my Android App using savedInstanceState and some custom adapter work. I have a nested view (vertical with horizontal scrollviews contained within) and noticed while scrolling that the horizontal lists will reset to an index of 0 once the view is off the screen and more than likely recycled.
I have looked into ScrollViewReader but could not figure out how to listen to the currently viewable index range ( in order to reset it later ). The other option seems to be using GeometryReader to do some view calculation, but that seems a bit off while using LazyGrids.
An example of the view that I am describing:
var body: some View {
GeometryReader { geometry in
ZStack(alignment: .top){
VStack {
HStack {
// header stuff
}
ScrollView(showsIndicators: false) {
// Vertical list
LazyVGrid(columns: gridItemLayout, spacing: 17) {
ForEach(parentData, id: \.key) { parent in
Text(parent.title)
ZStack {
Image("image")
ScrollViewReader { scrollView in
ScrollView(.horizontal, showsIndicators: false) {
// Horizontal lists
LazyHGrid(rows: gridItemLayout, spacing: 20){
ForEach(data, id: \.key) { data in
Button(action: {
//action
}) {
FeaturedCellView(data: data).equatable()
}
}
}.fixedSize(horizontal: true, vertical: false)
}
}
}
}.fixedSize(horizontal: false, vertical: true)
}
}
}
}
}
}
Examples of the views saving states can be found in apps like Netflix, or even in the App Store's horizontal grids.
I was hoping someone had solved this before and if so what you did.
Thanks for your help!
I have HStack and ScrollView in ZStack, how can I prevent the HStack from covering the bottom of ScrollView? I think there is a modifier to fix this issue, but I can’t remember it.
struct Messages: View {
var body: some View {
ZStack(alignment: .bottom) {
ScrollView {
VStack() {
MessagesList()
}
}
MessageInput()
}
}
}
You can drop the ZStack and declare the view that sticks to the bottom using the .safeAreaInset modifier, e.g.:
ScrollView {
// etc.
}
.safeAreaInset(edge: .bottom) {
MessageInput()
}
More information on Hacking with Swift and Apple’s developer docs
Is it possible to create a custom horizontal indicator that has empty and filled circles to show how many images there are and the current position?
The below attempt uses a lazyHStack and OnAppear but, judging from the console output, it doesn't work properly since scrolling back and forth doesn't recall the onAppear consistently.
import SwiftUI
struct ContentView: View {
let horizontalScrollItems = ["wind", "hare.fill", "tortoise.fill", "rosette" ]
var body: some View {
GeometryReader { geometry in
ScrollView(.horizontal, showsIndicators: false) {
LazyHStack {
ForEach(horizontalScrollItems, id: \.self) { symbol in
Image(systemName: symbol)
.font(.system(size: 200))
.frame(width: geometry.size.width)
.onAppear(){print("\(symbol)")}
}
}
}
}
}
}
This is the desired indicator. I'm just not sure how to properly fill and empty each circle as the user scrolls back and forth. Appreciate the help!
You can get the desired result using TabView() and PageTabViewStyle()
Note : This will work from SwiftUI 2.0
Here is the code :
struct ContentView: View {
let horizontalScrollItems = ["wind", "hare.fill", "tortoise.fill", "rosette" ]
var body: some View {
GeometryReader { geometry in
TabView(){
ForEach(horizontalScrollItems, id: \.self) { symbol in
Image(systemName: symbol)
.font(.system(size: 200))
.frame(width: geometry.size.width)
}
}
.tabViewStyle(PageTabViewStyle())
.indexViewStyle(PageIndexViewStyle(backgroundDisplayMode: .always))
}
}
}
Result :
I'm trying to re-create UI of my current app using SwiftUI. And it is way more difficult than I initially though.
I wanted to achieve card-like cells with some background behind them. I found that List doesn't support that, at least yet. List is so limited - it doesn't allow you to remove cell separator.
So I moved to ForEach inside ScrollView. I guess that isn't something which should be used in production for long tables but that should work for now. The problem I have is that ForeEach view doesn't take all the width ScrollView provides. I can set .frame(...) modifier but that will require hardcoding width which I definitely don't want to do.
Any ideas how to force VStack take full width of the ScrollView? I tried to use ForeEach without VStack and it has the same issue. It seems like ScrollView (parent view) "tells" its child view (VStack) that its frame is less that actual ScrollView's frame. And based on that information child views build their layout and sizes.
Here is my current result:
And here is the code:
struct LandmarkList : View {
var body: some View {
NavigationView {
ScrollView() {
VStack {
Spacer().frame(height: 160)
ForEach(landmarkData) { landmark in
LandmarkRow(landmark: landmark).padding([.leading, .trailing], 16)
}
}.scaledToFill()
.background(Color.pink)
}
.background(Color.yellow)
.edgesIgnoringSafeArea(.all)
.navigationBarTitle(Text("Landmarks"))
}
}
}
struct LandmarkRow : View {
var landmark: Landmark
var body: some View {
HStack {
VStack(alignment: .leading) {
Text(landmark.name).font(.title)
Text("Subtitle")
.font(.callout)
.color(.gray)
}
Spacer()
Text("5 mi")
.font(.largeTitle)
}.frame(height: 80)
.padding()
.background(Color.white)
.cornerRadius(16)
.clipped()
.shadow(radius: 2)
}
}
I've got the same issue, the only way I have found so far is to fix the ScrollView and the content view width, so that every subview you add inside the content view will be centered.
I created a simple wrapper that take the width as init parameter
struct CenteredList<Data: RandomAccessCollection, Content: View>: View where Data.Element: Identifiable {
public private(set) var width: Length
private var data: Data
private var contentBuilder: (Data.Element.IdentifiedValue) -> Content
init(
width: Length = UIScreen.main.bounds.width,
data: Data,
#ViewBuilder content: #escaping (Data.Element.IdentifiedValue) -> Content)
{
self.width = width
self.data = data
self.contentBuilder = content
}
var body: some View {
ScrollView {
VStack {
ForEach(data) { item in
return self.contentBuilder(item)
}.frame(width: width)
}
.frame(width: width)
}
.frame(width: width)
}
}
By default it takes the screen width (UIScreen.main.bounds.width).
It works just like a List view:
var body: some View {
TileList(data: 0...3) { index in
HStack {
Text("Hello world")
Text("#\(index)")
}
}
}
Its possible that the answer to this might just be wrapping your scrollView inside of a GeometryReader
Like done in the answer here -> How do I stretch a View to its parent frame with SwiftUI?