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

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()
}
}

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.

SwiftUI NavigationLink not displaying view until physical rotation then all is ok?

The opening view of App is blank with a Back Button. However, the intended view will appear after complete rotation of physical device (before touching anything) or by tapping the Back Button on the displayed empty view.
This is a weird bug. After that rotation (to landscape and back to protrait) everything is peachy-keen-o.
I am aware that adding: .navigationViewStyle(StackNavigationViewStyle()) to the NavigationView will sorta resolve the problem. But this solution is a poor fix for my App's needs.
Is there a command to intentionally rotate device in code to resolve this? Or some other fix to get that first view to appear without counting on the user to rotate the device?
I've tried various solutions with no luck.
Below is a quick setup of the problem.
struct FirstView: View {
#Binding var viewNum: Int?
var body: some View {
VStack {
Text("View One")
.padding(20)
Button {
viewNum = 2
} label: {
Text("Go to 2nd view")
}
}
.onAppear {
viewNum = 1
}
}
}
struct MainView: View {
#State var selection: Int? = 1
var body: some View {
// VStack {
NavigationView {
List {
NavigationLink("First View", tag: 1, selection: $selection){ FirstView(viewNum: $selection)}
NavigationLink("Second View", tag: 2, selection: $selection){
Text("View 2").padding(20)
Button {
selection = 1
} label: {
Text("Go to first view")
}
}
}
}
// .navigationViewStyle(StackNavigationViewStyle())
//}
}
}

Back button skipping a view in View1 -> View2 -> View3 NavigationLink

I'm fairly new to Swift and it may be I'm going about this all wrong, but I have a bug I can't work out - within a NavigationView, a NavigationLink in View1 opens to View 2, then that view has a NavigationLink to View3. View3 shows the title from View1, which is obviously wrong, and when the back button is pressed it also goes back to View1 rather than View2. Am I using these links wrong or is it a SwiftUI issue? Simplified example of what I'm doing below.
struct FirstView: View {
var body: some View {
Text("Hello, World #1!")
.navigationBarItems(trailing:
NavigationLink(destination: SecondView()){
Image(systemName: "folder.badge.plus")
}
)
}
}
struct SecondView: View {
var body: some View {
Text("Hello, World #2!")
.navigationBarItems(trailing:
NavigationLink(destination: ThirdView()){
Image(systemName: "folder.badge.plus")
}
)
}
}
struct ThirdView: View {
var body: some View {
Text("Hello, World #3!")
}
}
Funny enough I had the same problem with the navigation skipping one view. Your problem is different though. The first view only will have the NavigationView declared. After that you just use navigation links.
Refactoring:
import SwiftUI
struct FirstView: View {
var body: some View {
NavigationView {
VStack {
Text("Hello, World #1!")
.padding()
NavigationLink(destination: SecondView()){
Image(systemName: "folder.badge.plus")
}
}
// a navigationBarTitle modifier inside!
.navigationBarTitle(Text("Hello"))
}
}
}
struct SecondView: View {
var body: some View {
VStack {
Text("Hello, World #2!")
.padding()
NavigationLink(destination: ThirdView()){
Image(systemName: "folder.badge.plus")
}
}
}
}
struct ThirdView: View {
var body: some View {
Text("Hello, World #3!")
}
}
struct FirstView_Previews: PreviewProvider {
static var previews: some View {
FirstView()
}
}

How to disable ScrollView Bounce In SwiftUI

Any Modifier available to stop bounce of ScrollView in swiftUI ?
struct RoomDetailsView: View {
var body: some View {
ScrollView(showsIndicators: false) {
Image("test")
Text("Hello Text")
...
...
}
}
}
I tried below code but it not work for me. looks like it deprecated
ScrollView(alwaysBounceVertical: true) {
Image("test")
Text("Hello Text")
...
...
}
try using this line of code:
UIScrollView.appearance().bounces = false
You can use it like this:-
struct RoomDetailsView: View {
init() {
UIScrollView.appearance().bounces = false
}
var body: some View {
ScrollView(showsIndicators: false) {
Image("test")
Text("Hello Text")
...
...
}
}
}
Or you can write this line in AppDelegate to apply this behaviour throughout into your app.
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
UIScrollView.appearance().bounces = false
}
You may use SwiftUI-Introspect library:
ScrollView {
// some content
}
.introspectScrollView { scrollView in
scrollView.alwaysBounceVertical = false
}
Set the ScrollView's showsIndicators parameter equal to false so the user interacting with your view doesn't activate the scroll indicator (it can happen even without scrolling enabled).
ScrollView(showsIndicators: false)
In the onAppear modifier for the ScrollView, add this line.
UIScrollView.appearance().bounces = false
In the onDisappear modifier for the ScrollView, add this line.
UIScrollView.appearance().bounces = true
If you set UIScrollView.appearance().bounces equal to false in the init, it will prevent all of your app's ScrollViews from bouncing. By setting it equal to true in the onAppear modifier and equal to false in the onDisappear modifier, you ensure that it only effects the one ScrollView.
Your ScrollView should look something like this.
ScrollView(showsIndicators: false) {
...
...
...
}
.onAppear {
UIScrollView.appearance().bounces = false
}
.onDisappear {
UIScrollView.appearance().bounces = true
}
A better solution would be to use viewbuilder and create your own scrollview that doesn't bounce when the content size is less than scrollview frame size.
import SwiftUI
struct BounceVerticalDisableScrollView<Content: View>: View {
#State private var alwaysBounceVertical = false
let content: Content
init(#ViewBuilder content: () -> Content) {
self.content = content()
}
var body: some View {
GeometryReader { scrlViewGeometry in
ScrollView {
content
.background(
GeometryReader {
// calculate height by clear background
Color.clear.preference(key: SizePreferenceKey.self,
value: $0.frame(in: .local).size.height)
}.onPreferenceChange(SizePreferenceKey.self) {
self.alwaysBounceVertical = $0 < scrlViewGeometry.size.height
}
)
}
// disable scroll when content size is less than frame of scrollview
.disabled(self.alwaysBounceVertical)
}
}
}
// return size
public struct SizePreferenceKey: PreferenceKey {
public static var defaultValue: CGFloat = .zero
public static func reduce(value: inout CGFloat, nextValue: () -> CGFloat) {
value += nextValue()
}
}
So, that you can use the scrollview as:
var body: some View {
VStack (alignment: .leading, spacing: 10) {
BounceVerticalDisableScrollView{
VStack (alignment: .leading, spacing: 10) {
....
....
}.padding()
}
}
}
Apple introduced an new modifier named scrollBounceBehavior with iOS 16.4 that can be used to prevent the ScrollView from bouncing when the content is smaller than the screen.
https://developer.apple.com/documentation/swiftui/view/scrollbouncebehavior(_:axes:)
struct RoomDetailsView: View {
var body: some View {
ScrollView(showsIndicators: false) {
Image("test")
Text("Hello Text")
...
...
}
.scrollBounceBehavior(.basedOnSize)
}
}

ScrollView SwiftUI

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.