How to disable opacity with transitions in SwiftUI? - swift

I'm using SwiftUI.
I have a transition that I'm using to bring up my SignInView. However, this transition seems to be automatically applying an opacity effect on the view it's replacing. This wouldn't be a problem, however, it seems like the Safe Area on both the top and the bottom have different rates of receiving the opacity than the rest of the view.
I'm trying to find 1 of 2 solutions:
How can I get rid of the opacity effect altogether, or
How can I get the opacity effect to be applied evenly everywhere.
Here is the code for my transition:
struct AuthView: View {
#State var showSignIn: Bool = false
var body: some View {
ZStack {
if !showSignIn {
WelcomeView(showSignIn: $showSignIn)
} else {
SignInView(showSignIn: $showSignIn)
.transition(AnyTransition.move(edge: .trailing))
.zIndex(1)
}
}
}
}
Here is the button controlling the state variable:
Button(action: { withAnimation(.easeInOut) { showSignIn.toggle() } }) {
//Button text
}
I also have a video (GIF) that better shows what I'm talking about, when I mention the uneven distribution of opacity.
https://i.stack.imgur.com/KXEcF.gif
If you look closely at the top and bottom safe area it fades faster than the rest of the view, which is undesired.
Note: When I changed the appearance to Dark Mode, it turned black instead of white on the top and bottom.

I assume you want identity transition for original view (opacity transition is applied by default if none other specified)
if !showSignIn {
WelcomeView(showSignIn: $showSignIn)
.transition(.identity) // << here !!

Related

Custom NavigationView + Status Bar for only one view in SwiftUI

I have an application with a blue background main screen and other white background screens. As I'm using custom colors for the entire app, I decided to use .preferredColorScheme(.light) on the root file (SomethingApp.swift) otherwise it would be a complete mess on dark mode.
#main
struct SomethingApp: App {
var body: some Scene {
WindowGroup {
HomeView()
.preferredColorScheme(.light)
}
}
}
But the result for this main screen is particularly ugly!
I get a black status bar on a blue background and the white navigation title is almost invisible on scroll (white navigation background + bottom border).
So, I've looked for a better solution:
1 - Using .preferredColorScheme(.dark) just for this main screen. The result should be a white status bar and a dark NavigationView on scroll. But no matter where I put this modifier, nothing happens!
2 - Customizing the NavigationView inside an init(). It would be awesome to only have the blur effect on scroll or maybe a very light white background so the title could still be visible. But I've only managed to make the background disappears on scroll (see below).
init() {
let navBarAppearance = UINavigationBarAppearance()
navBarAppearance.configureWithTransparentBackground()
UINavigationBar.appearance().standardAppearance = navBarAppearance
// UINavigationBar.appearance().compactAppearance = navBarAppearance
// UINavigationBar.appearance().scrollEdgeAppearance = navBarAppearance
}
Do you have any other ideas? Thanks.
EDIT
I have just noticed that this init() is applied to all the views which is not what I wanted lol.
Also, I have just discovered to perform the "1 -" by using .colorScheme(.dark) on the NavigationView but unfortunately it doesn't changed the status bar color. But it's a progress!

SwiftUI - Position an overlay relative to its anchor

I have a ZStack containing 2 views:
referenceContent - has some content and a Divider. This is the main content across the screen
popoverContent - is a conditional popup window that only takes up a tiny portion of the screen.
var body: some View {
ZStack {
referenceContent
if popoverCondition {
popoverContent
}
}
}
I want the popoverContent's top edge to line up with the bottom of referenceContent
Anyone know how to make this happen? Or is there just a much better way to view this popup window than I'm doing now? Thanks!
You can do this using the overlay(alignment:content:) modifier (previously overlay(_:alignment:)) in combination with custom alignment guides.
The basic idea is that you align the bottom of your reference view with the top of your popover view.
The annoying thing is that the overlay modifier only lets you specify one alignment guide (for the two views). So if you write stack1.overlay(alignment: .bottom) { stack2 } it will align the bottom of your reference with the bottom of your overlay. A quick way to overcome this is to overwrite the bottom alignment guide of your overlay and return the top instead.
referenceView
.overlay(alignment: .bottom) {
popoverContent
// overwrites bottom alignment of the popover with its top alignment guide.
.alignmentGuide(.bottom) {$0[.top]}
}
Overlay vs ZStack
You might ask: "why don't you use a ZStack instead of an overlay?". Well the difference between the two is that the ZStack will take the size of your popover into consideration when laying out your entire view (reference + popover). That is the opposite of what a popover should do. For a popover, the layout system should only take the size of your reference view into consideration and draw the popover on top of it (without affecting the layout of your reference). That is exactly what the overlay(...) modifier does.
Old API (prior to iOS 15, macOS 12)
In older versions of SwiftUI the arguments of the overlay modifier were in reverse order. So the code example for these older systems is:
referenceView
.overlay(
popoverContent.alignmentGuide(.bottom) {$0[.top]},
alignment: .bottom
)
Custom alignment guides
When you don't want to overwrite an existing alignment guide (because you need it somewhere else for example) you can also use a custom alignment guide. Here is a more generic example using a custom alignment guide named Alignment.TwoSided
extension View {
#available(iOS 15.0, *)
func overlay<Target: View>(align originAlignment: Alignment, to targetAlignment: Alignment, of target: Target) -> some View {
let hGuide = HorizontalAlignment(Alignment.TwoSided.self)
let vGuide = VerticalAlignment(Alignment.TwoSided.self)
return alignmentGuide(hGuide) {$0[originAlignment.horizontal]}
.alignmentGuide(vGuide) {$0[originAlignment.vertical]}
.overlay(alignment: Alignment(horizontal: hGuide, vertical: vGuide)) {
target
.alignmentGuide(hGuide) {$0[targetAlignment.horizontal]}
.alignmentGuide(vGuide) {$0[targetAlignment.vertical]}
}
}
}
extension Alignment {
enum TwoSided: AlignmentID {
static func defaultValue(in context: ViewDimensions) -> CGFloat { 0 }
}
}
You would use that like this:
referenceView
.overlay(align: .bottom, to: .top, of: popoverContent)

ScrollView nested in TabView reset position to start while changing tab

I'm experimenting with SwiftUI and I found a weird behaviour of ScrollView when nested in a TabView. If I swipe the ScrollView down and then do a slow gesture to swipe to the next tab, it sometimes reset the scrollView Position to the start. I'm not sure how to explain it well so here is a gif:
I'm trying to understand what cause this and if there is a way to avoid it.
Here is the code I use:
var body: some View {
TabView(selection: $selectedTab,
content: {
ForEach(coreData.pageList, id: \.self) { page in
if(coreData.pageList[page] == nil) {
ProgressView().onAppear(perform: {
loadPage(page: page)
})
} else {
ScrollView {
Text("START OF PAGE ------- -----Page Number: \(page)! This is a very short text made big to simulate scrolling. This is a very short text made big to simulate scrolling. ").font(.system(size: 90))
}
}
}
})
}
Am I missing something simple, is it a bug (with workaround), am I using these views wrong?
Ultimately I just want to have something that look like a page that you swipe right left or right but having the scroll position reset so fast would not be great user experience and doesn't look great
Edit: I haven't found any solution yet, but an interesting point: if I put the ProgressView inside the scrollView the scrollview does NOT reset position but the next tab will already be scrolled with the same amount as the previews page. This is equally "bad" if not worse :-(.
Try to put it into separated view (passing all needed parameters), so SwiftUI will see that view not changed and so not rerender it, ie. it should look like
if(coreData.pageList[page] == nil) {
ProgressView().onAppear(perform: {
loadPage(page: page)
})
} else {
DetailsView(page: page) // << ScrollView inside
}

SwiftUI gap left margin and change color of List item's bottom border

I'm new to SwiftUI and trying to create a UI component like UITableView. I'm using List and there's a couple of things I'd like to customize.
There's a margin on the left. I managed to gap the margin for components inside the item but not the bottom border.
I'd like to change the color of the List item's bottom border.
Here is how my code looks:
List {
ForEach(someArray) { _ in
Text("Item")
.listRowInsets(EdgeInsets()) // To gap the left margin
}
}
To be specific about the List item's bottom border, I will attach this image.
The line pointed by the red arrow is the List item's bottom border I mean. Not just the last border but each item's border.
I would appreciate any insight. Thank you!
SwiftUI is still missing some features known from UIKit. This will certainly change in the course of time, but for now I would recommend a slightly different approach. You could achieve the desired result by using a ScrollView in combination with a LazyVStack (iOS14)
ScrollView {
LazyVStack(alignment: .leading) {
ForEach(someArray, id: \.self) { _ in
Text("Item")
.padding(.leading)
Divider()
.background(Color.red)
}
}
}

SwiftUI: View flashes briefly white [duplicate]

This question already has answers here:
How to prevent initial white flash when showing a UIWebView?
(4 answers)
Closed 2 years ago.
I'm a beginner SwiftUI programmer and I'm encountering the following problem:
I have two Views, one loading view (Rectangle) and one web view (WKWebView).
Using those two views like this:
var body : some View {
ZStack {
WebView(self.webView)
Rectangle()
.foregroundColor(self.state.isLoading ? self.loadingColor : Color.clear)
}
.onAppear {
// Initialize web view etc.
// Sets self.state.isLoading to false after some time has passed
}
}
Interestingly when self.state.isLoading changes from true to false the UI briefly flashes white.
I don't know why this is happening, especially because the WebView definitely isn't white at the point of showing.
I tried adding a background rectangle and setting the backgroundColor of self.webView and self.webView.scrollView:
var body : some View {
ZStack {
Rectangle()
.foregroundColor(Color.red)
WebView(self.webView)
Rectangle()
.foregroundColor(self.state.isLoading ? self.loadingColor : Color.clear)
}
.onAppear {
// Initialize web view etc.
self.webView.backgroundColor = UIColor.red
self.webView.scrollView.backgroundColor = UIColor.red
// Sets self.state.isLoading to false after some time has passed
}
}
I expected to see a brief red screen, but it remains white.
Adding an animation to the Rectangle() transition makes the flashing go away
Rectangle()
.foregroundColor(self.state.isLoading ? self.loadingColor : Color.clear)
.animation(.easeIn)
Am I missing something or doing something wrong?
I'm thinking this may have something to do with the self.state.isLoading variable and when it changes, the whole view is reloading. Can you try instead making the isLoading a local variable in the struct (#State var isLoading: Bool = false) and then toggling the local variable instead?