ScrollView SwiftUI - scrollview

I'am developing an app with swift ui. It's a single View App. But I have a problem with the scrollView
I have tried to delete the scrollview but it didn't change anything. I always have this big white problem on the top of my view.
https://imgur.com/a/BiWy0fe
import SwiftUI
struct Redactor {
var id: Int
let name, imageURL, since, bio: String
}
struct Article {
var id: Int
let name, image, content, redactor: String
}
struct MainScreenView: View {
let redactors:[Redactor] = [...]
let articles:[Article] = [...]
var body: some View {
NavigationView {
ScrollView(.vertical, showsIndicators: true) {
VStack(alignment: .leading) {
ScrollView(.horizontal, showsIndicators: false) {
HStack(alignment: .top, spacing: 20) {
ForEach(redactors, id: \.id) { Redactor in
NavigationLink(destination: MainRedactorView(redactor: Redactor)) {
RedactorView(redactor: Redactor)
}
}
}.padding(.leading, 15)
}.padding(.top, 10)
VStack(alignment: .leading) {
Text("Les Dernières nouvelles :")
.font(.title)
.fontWeight(.bold)
.padding(.leading, 15)
.padding(.top, 20)
ForEach(articles, id: \.id) { Article in
ArticleView(article: Article)
}
}
}
}
.navigationBarTitle(Text("The News Place"))
}
}
}
struct RedactorView: View {
let redactor : Redactor
var body : some View {
VStack {
Image(redactor.imageURL)
.resizable()
.frame(width: 150, height: 150)
.aspectRatio(contentMode: .fill)
.cornerRadius(75)
Text(redactor.name)
.font(.subheadline)
.fontWeight(.bold)
}
}
}
struct ArticleView: View {
var article: Article
var body: some View {
VStack (alignment: .leading) {
Image(article.image)
.renderingMode(.original)
.resizable()
.cornerRadius(10)
.aspectRatio(contentMode: .fit)
Text(article.name)
.font(.title)
.fontWeight(.bold)
Text("Le Rondeau Mag'")
.font(.subheadline)
.foregroundColor(.red)
Text(article.content)
.font(.body)
.lineLimit(5)
.foregroundColor(.secondary)
}.padding()
}
}
I would like to delete this white blank on the top of my scroll view. Thank you

Did you try deleting the VStack inside ScrollView? ScrollView already defines an implicit stack.
A good way to know which element causes the blank space is applying .border(Color.red) to different views.
Hope this helps.

Is the NavigationView that's creating the white space on top.
Try with
.navigationBarTitle(Text("The News Place"), displayMode: .inline)
and see if the white space disappears.

Related

.isFavorite results in errors

I'm trying to place an inFavorite on a Detailed View but get the following errors:
-Cannot convert value of type 'Binding' to expected condition type 'Bool'
-Value of type 'ObservedObject.Wrapper' has no dynamic member 'isFavorite' using key path from root type 'TitleModel'
import SwiftUI
struct DetailView: View {
#EnvironmentObject var model: TitleModel
var detail: Title
var body: some View {
ScrollView {
VStack (alignment: .leading) {
//MARK: Detail Image
Image(detail.image1)
.resizable()
.scaledToFill()
//MARK: Divider
Divider()
if $model.isFavorite {
Image(systemName: "star.fill")
.imageScale(.medium)
.foregroundColor(.yellow)
}
Divider()
//MARK: Remark
VStack(alignment: .leading) {
Text("Remark:")
.font(.headline)
.padding([.bottom, .top], 5)
ForEach(detail.remark, id:\.self) {item in
Text("• " + item)
}
}
.padding(.horizontal)
//MARK: Reference(s)
VStack(alignment: .leading) {
Text("Reference(s):")
.font(.headline)
.padding([.bottom, .top], 5)
}
.padding(.horizontal)
}
}
.navigationBarTitle(detail.title)
}
}
struct DetailView_Previews: PreviewProvider {
static var previews: some View {
//create a dummy title and pass it into the detail view so we can see a preview
let model = TitleModel()
DetailView(detail: model.titles[0])
}
}
The view works perfect until I throw in the .isFavorite code. 'var model' references my View Model for my JSON data 'titles'. I am attempting to allow the person to tap the .star to highlight the view. I will then have a Scroll View with only the favorites. Thanks for helping me out.

In SwiftUI, supporting iOS 14 and above, how do I get a ScrollView child view to ignore the safe area

I have a VStack that contains a Rectangle. The Rectangle has an edgesIgnoresSafeArea(.top) view modifier on it to extend it through the top safe area.
import SwiftUI
struct ScrollViewSafeArea: View {
var body: some View {
ScrollView {
VStack {
Rectangle()
.fill(Color.orange)
.frame(height: 200)
.edgesIgnoringSafeArea(.top)
Spacer()
}
}
.background(Color.green.ignoresSafeArea())
}
}
Nice!
However, when I embed this inside a ScrollView, the Rectangle no longer extends through the safe area.
import SwiftUI
struct ScrollViewSafeArea: View {
var body: some View {
ScrollView {
VStack {
Rectangle()
.fill(Color.orange)
.frame(height: 200)
.edgesIgnoringSafeArea(.top)
Spacer()
}
}
.background(Color.green.ignoresSafeArea())
}
}
I can add a negative top padding to the Rectangle to extend it through the safe area, but this feels hacky to me.
Does anyone have a better way?
So I figured it out. I feel kind-of stupid actually. I wasn't ignoring the safe areas properly. Here is the code that does what I want it to do.
import SwiftUI
struct ScrollViewSafeArea: View {
var body: some View {
ScrollView {
VStack {
Rectangle()
.fill(Color.orange)
.frame(height: 200)
Spacer()
}
}
.ignoresSafeArea()
// Need to ignoreSafeArea again on the background color so
// the color extends to the horizontal edges in landscape.
.background(Color.green.ignoresSafeArea())
}
}
struct ScrollViewSafeArea_Previews: PreviewProvider {
static var previews: some View {
ScrollViewSafeArea()
}
}

SwiftUI NavigationView, going back if NavigationLink is inside a NavigationBarItem

As there are some problems with iOS 13.4 and Xcode 11.4 with presentationMode.wrappedValue.dismiss() I am looking for an alternative approach to go back programmatically. I found this solution from MScottWaller:
iOS SwiftUI: pop or dismiss view programmatically
Unfortunately, in my case it does not work:
struct MasterView: View {
#State private var showDetail = false
var body: some View {
VStack {
Text("MasterView")
.navigationBarItems(trailing: HStack {
NavigationLink(destination: DetailView(showSelf: $showDetail), isActive: $showDetail) {
Image(systemName: "tag")
.padding(.leading, 4)
}
})
}
}
}
struct DetailView: View {
#Binding var showSelf: Bool
var body: some View {
Button(action: {
self.showSelf = false
}) {
Text("Pop")
}
}
}
If the NavigationLink is inside a navigationBarItem, I can't go back from my DetailView. I don't know if it is a bug or there are other reasons why NavigationLink does not work in the same way inside a navigationBarItem.
As a workaround I use this variant with a empty NavigationLink inside the view. It works, but I don't like this:
struct MasterView: View {
#State private var showDetail = false
var body: some View {
VStack {
Text("MasterView")
NavigationLink(destination: DetailView(showSelf: $showDetail), isActive: $showDetail) {
EmptyView()
}
.navigationBarItems(trailing: HStack {
Button(action: { self.showDetail.toggle() }) {
Image(systemName: "tag")
.padding(.leading, 4)
}
})
}
}
}
Any ideas why the NavigationLink does not correct work inside a navigationBarItem?
It's an iOS bug.
https://forums.developer.apple.com/thread/125937
The work around is to toggle a NavigationLink hidden outside of nav bar:
struct Parent: View {
#State private var showingChildView = false
var body: some View {
NavigationView {
VStack {
Text("Hello World")
NavigationLink(destination: Child(),
isActive: self.$showingChildView)
{ Text("HiddenLink").hidden() }
}
.navigationBarItems(trailing: Button(action:{ self.showingChildView = true }) { Text("Next") })
}
}
}

SwiftUI, ScrollView pushing VStack images up in ZStack

When I apply the scrollview it pushes my whole view up and leaves a white space below the bottom of the ZStack. Not sure why but if someone can please help.
var body: some View {
ZStack{
GeometryReader { geometry in
Spacer()
ScrollView (.vertical, showsIndicators: false) {
VStack (spacing : self.Vspacing){
Spacer()
ForEach (self.buttons, id: \.self) { row in
SportsButtonsRow(screenWidth: geometry.size.width,
Hspacing: self.Hspacing,
buttons: row, didTapButton: { sportButton in self.displayText = sportButton.title}
)
}//end foreach
// Spacer ()
}.background(Color.black)//end of VStack
}//end of scroll view
}//close geometry reader
}//end of ZStack
.edgesIgnoringSafeArea(.all)
}//end of main some View
}
You need to change the order of containers construction. The following scratch demo works:
var body: some View {
ZStack{
GeometryReader { geometry in
ScrollView (.vertical, showsIndicators: false) {
// YOUR CONTENT HERE
}
.background(Color.black)//end of VStack
.frame(height: geometry.size.height)
//end of scroll view
} //close geometry reader
.edgesIgnoringSafeArea(.all)
} //end of ZStack
} //end of main some View
I figured it out. It seems you need to designate a frame size for the scrollview so I did.
GeometryReader { geometry in
Spacer()
VStack{
Text ("fdklafdfa")
.foregroundColor(.blue)
.frame(width: geometry.size.width, height:geometry.size.height * 0.32 , alignment: .bottom)
Spacer()
ScrollView (.vertical, showsIndicators: false) {
VStack (spacing : self.Vspacing){
Spacer()
ForEach (self.buttons, id: \.self) { row in
SportsButtonsRow(screenWidth: geometry.size.width,
Hspacing: self.Hspacing,
buttons: row, didTapButton: { sportButton in self.displayText = sportButton.title}
)
}//end foreach
// Spacer ()
}//end of VStack
}//end of scroll view
.frame(width: geometry.size.width, height:geometry.size.height * 0.68 , alignment: .bottom)
.edgesIgnoringSafeArea(.all)
.background(Color.black)
}//End of main VStack
}//close geometry reader

Equal widths of subviews with SwiftUI

I'm trying to build a simple watchOS UI with SwiftUI with two pieces of information side-by-side above a button.
I'd like each side (represented as a VStack within an HStack) to take up half of the available width (so it's an even 50/50 split within the yellow parent view) divided where the | character is centered on the button in the example below.
I want the Short and Longer!!! text to each be centered within each side's 50%.
I started with this code, to get the elements in place and show the bounds of some of the different stacks:
var body: some View {
VStack {
HStack {
VStack {
Text("Short").font(.body)
}
.background(Color.green)
VStack {
Text("Longer!!!").font(.body)
}
.background(Color.blue)
}
.frame(minWidth: 0, maxWidth: .infinity)
.background(Color.yellow)
Button (action: doSomething) {
Text("|")
}
}
}
Which gave me this result:
Then, when it comes to making each side-by-side VStack 50% of the available width, I'm stuck. I thought it should work to add .relativeWidth(0.5) to each VStack, which should, as I understand it, make each VStack half the width of its parent view (the HStack, with the yellow background):
var body: some View {
VStack {
HStack {
VStack {
Text("Short").font(.body)
}
.relativeWidth(0.5)
.background(Color.green)
VStack {
Text("Longer!!!").font(.body)
}
.relativeWidth(0.5)
.background(Color.blue)
}
.frame(minWidth: 0, maxWidth: .infinity)
.background(Color.yellow)
Button (action: doSomething) {
Text("|")
}
}
}
But this is the result I get:
How can I get the behavior I want with SwiftUI?
Update: After reviewing the SwiftUI documentation more, I see the example here that sets a frame and then defines a relative width in comparison to that frame, so maybe I'm not supposed to use relativeWidth in this way?
I'm a step closer to what I want with the following code:
var body: some View {
VStack {
HStack {
VStack {
Text("Short").font(.body)
}
.frame(minWidth: 0, maxWidth: .infinity)
.background(Color.green)
VStack {
Text("Longer!!!").font(.body)
}
.frame(minWidth: 0, maxWidth: .infinity)
.background(Color.blue)
}
.frame(minWidth: 0, maxWidth: .infinity)
.background(Color.yellow)
Button (action: doSomething) {
Text("|")
}
}
}
which produces this result:
Now, I am trying to figure out what's creating that extra space in the middle between the two VStacks. So far, experimenting with getting rid of padding and ignoring safe areas does not seem to affect it.
I'm still confused about when and how relativeWidth is supposed to be used, but I was able to achieve want I wanted without using it. (EDIT 18 July 2019: According to the iOS 13 Beta 4 release notes, relativeWidth is now deprecated)
In the last update to my question I had some extra spacing between the two sides, and realized that was the default spacing coming in on the HStack and I was able to remove that by setting its spacing to 0. Here's the final code and result:
var body: some View {
VStack {
HStack(spacing: 0) {
VStack {
Text("Short").font(.body)
}
.frame(minWidth: 0, maxWidth: .infinity)
.background(Color.green)
VStack {
Text("Longer!!!").font(.body)
}
.frame(minWidth: 0, maxWidth: .infinity)
.background(Color.blue)
}
.frame(minWidth: 0, maxWidth: .infinity)
.background(Color.yellow)
Button (action: doSomething) {
Text("|")
}
}
}
And here is the result:
Here's how to create an EqualWidthHStack for watchOS 9, iOS 16, tvOS 16 & macOS 13
Here's the usage:
struct ContentView: View {
private let strings = ["Hello,", "very very very big", "world!"]
var body: some View {
EqualWidthHStack {
ForEach(strings, id: \.self) { string in
ZStack {
RoundedRectangle(cornerRadius: 10, style: .continuous)
.opacity(0.2)
Text(string)
.padding(10)
}
}
}
}
}
First create a struct that conforms to Layout.
struct EqualWidthHStack: Layout {
...
}
It will come with two default methods, here's how you can implement them.
Size that Fits:
func sizeThatFits(proposal: ProposedViewSize, subviews: Subviews, cache: inout ()) -> CGSize {
let maxSize = maxSize(subviews: subviews)
let spacing = spacing(subviews: subviews)
let totalSpacing = spacing.reduce(0.0, +)
return CGSize(width: maxSize.width * CGFloat(subviews.count) + totalSpacing,
height: maxSize.height)
}
Place Subviews:
func placeSubviews(in bounds: CGRect, proposal: ProposedViewSize, subviews: Subviews, cache: inout ()) {
let maxSize = maxSize(subviews: subviews)
let spacing = spacing(subviews: subviews)
let sizeProposal = ProposedViewSize(width: maxSize.width,
height: maxSize.height)
var x = bounds.minX + maxSize.width / 2
for index in subviews.indices {
subviews[index].place(at: CGPoint(x: x, y: bounds.midY),
anchor: .center,
proposal: sizeProposal)
x += maxSize.width + spacing[index]
}
}
You will need the following two helper methods.
Max Size:
private func maxSize(subviews: Subviews) -> CGSize {
let subviewSizes = subviews.map { $0.sizeThatFits(.unspecified) }
let maxSize: CGSize = subviewSizes.reduce(.zero, { result, size in
CGSize(width: max(result.width, size.width),
height: max(result.height, size.height))
})
return maxSize
}
Spacing:
private func spacing(subviews: Subviews) -> [CGFloat] {
subviews.indices.map { index in
guard index < subviews.count - 1 else { return 0.0 }
return subviews[index].spacing.distance(to: subviews[index + 1].spacing,
along: .horizontal)
}
}
Here's Apples WWDC22 video on how to make it:
Compose custom layouts with SwiftUI
You have set background of HStack to yellow color and HStack has some default inter child views spacing. By adding spacing: 0 in HStack will solve the problem see the updated code below.
var body: some View {
VStack {
HStack(spacing: 0) { // Set spacing here
VStack {
Text("Short").font(.body)
}
.frame(minWidth: 0, maxWidth: .infinity)
.background(Color.green)
VStack {
Text("Longer!!!").font(.body)
}
.frame(minWidth: 0, maxWidth: .infinity)
.background(Color.blue)
}
.frame(minWidth: 0, maxWidth: .infinity)
.background(Color.yellow)
Button (action: doSomething) {
Text("|")
}
}
}