Add animated transition between gradient background change in SwiftUI - animation

I have this code below
import SwiftUI
struct MyUIView: View {
let timer = Timer.publish(every: 0.6, on: .main, in: .common).autoconnect()
#State var gradientBackgroundArray = [Constants.gradientFirst, Constants.gradientSecond, Constants.gradientThird]
var body: some View{
HStack(){
ZStack(){}
.frame(maxWidth: .infinity, maxHeight: 20)
.background(gradientBackgroundArray[0].animation(.easeInOut(duration: 0.4)))
Spacer()
ZStack(){}
.frame(maxWidth: .infinity, maxHeight: 20)
.background(gradientBackgroundArray[0].animation(.easeInOut(duration: 0.4)))
Spacer()
ZStack(){}
.frame(maxWidth: .infinity, maxHeight: 20)
.background(gradientBackgroundArray[0].animation(.easeInOut(duration: 0.4)))
}
.padding(10)
.onReceive(timer){ _ in
gradientBackgroundArray.shuffle()
}
}
}
class Constants {
public static let gradientFirst = LinearGradient(gradient: Gradient(colors: [.red, .blue]), startPoint: .leading, endPoint: .trailing)
public static let gradientSecond = LinearGradient(gradient: Gradient(colors: [.black, .yellow]), startPoint: .leading, endPoint: .trailing)
public static let gradientThird = LinearGradient(gradient: Gradient(colors: [.green, .white]), startPoint: .leading, endPoint: .trailing)
}
Now I shuffle gradientBackgroundArray every 0.6sec and it works fine. But the issue is that I want to add animation or transition effect to the background change after shuffling the values using this line of code:
.background(gradientBackgroundArray[2].animation(.easeInOut(duration: 0.4)))
But there's no animation or transition effect, it just changes instantly.
Please how can I achieve this?

In SwiftUI you can’t easily animate from one set of colours to another.
You can animate the startPoint and endPoint, or the grayScale, saturation, or hueRotation, using one these modifiers…
.grayscale(_ amount: Double)
.hueRotation(_ angle: Angle)
.saturation(_ amount: Double)
If you want to animate the colours themselves, you'll need to create an AnimatableModifier, a full implementation of which can be found here.

Related

view display on the right of the window

hello i have an issue with my project on Xcode when I use navigation view, the window display not normally and appear on the right of the window already displayed,
here is the code.
NavigationLink(destination: Acceuil())
{
HStack{
Image("icone_connexion")
.font(.system(size: 15))
// .scaledToFill()
Text("se connecter")
.font(.system(size: 30))
}.cornerRadius(60)
.frame(width: 400, height: 60)
} .background(Capsule().fill(Color(red: 55/255, green: 66/255, blue: 114/255, opacity:1)))
.frame(width: 400, height: 60) //fin navigationlink
.buttonStyle(PlainButtonStyle())
I would like that the new window replace the older one:)
On macOS it is the standard behavior in NavigationView to show the views (parent view and destination view) next to each other: https://developer.apple.com/design/human-interface-guidelines/macos/windows-and-views/column-views/
If you don't want that you can do:
NavigationView {
...
}
.navigationViewStyle(.stack)
so I found this code
import SwiftUI
struct ContentView: View {
#State private var show = false
var body: some View {
VStack{
if !show {
RootView(show: $show)
.frame(maxWidth: .infinity, maxHeight: .infinity)
.background(Color.blue)
.transition(AnyTransition.move(edge: .leading)).animation(.default)
}
if show {
NextView(show: $show)
.frame(maxWidth: .infinity, maxHeight: .infinity)
.background(Color.green)
.transition(AnyTransition.move(edge: .trailing)).animation(.default)
}
}
}
}
struct RootView: View {
#Binding var show: Bool
var body: some View {
VStack{
Button("Next") { self.show = true }
Text("This is the first view")
}
}
}
it work grate for me so thanks you for your help

How to set a max width on a subview of HSplitView

I'm having some trouble with HSplitView on macOS, and I'm not sure if it's a bug or something I'm doing wrong.
I'm trying to create an app with a [Sidebar | Main Content | Inspector] layout. This is similar to the SF Symbols app, where you can view more information about a selected icon.
The approach I've taken is to have the Main Content and Inspector views be in an HSplitView. Having three views in a NavigationView only seems to use the Mail pattern of [List | List | Detail], which is not the layout I'm looking for.
The problem I'm seeing is that when I set a maxWidth on the Inspector view and drag the divider to the left, the Main Content view resizes to be smaller than the available space. The parent HSplitView doesn't resize, so I'm not sure if I'm using the wrong modifiers or using them in the wrong places.
I've setup a basic test case on github. If y'all could offer and help or guidance, that would be amazing.
Observed behaviour
Expected behaviour
import SwiftUI
struct A: View {
var body: some View {
VStack {
Spacer()
HStack {
Spacer()
Text("Pane A")
Spacer()
}
Spacer()
}
.frame(
minWidth: 200,
maxWidth: .infinity,
maxHeight: .infinity,
alignment: .leading
)
.background(Color.red)
.foregroundColor(.black)
.layoutPriority(1)
}
}
struct B: View {
var body: some View {
VStack {
Spacer()
HStack {
Spacer()
Text("Pane B")
Spacer()
}
Spacer()
}
.frame(
minWidth: 200,
idealWidth: 250,
maxWidth: 300,
maxHeight: .infinity
)
.background(Color.purple)
.foregroundColor(.black)
}
}
struct ContentView: View {
var body: some View {
HSplitView {
A()
B()
}
.frame(
minWidth: 0,
maxWidth: .infinity,
maxHeight: .infinity
)
.background(Color.blue)
}
}e
Thanks!
I've had the same problem for several days, and I solved it in a flash.
Need to drag View
HSplitView {
View1()
View2()
}
Intuitively, we will set the maximum width of View1, but when SwiftUI drags, View1 exceeds the maximum width, it will display an inexplicable error
HSplitView {
View1()
.frame(maxWidth: 300)
View2()
}
Solutions
Set the minimum width of View2, and let View automatically fill all spaces. There will be no drag problem
HSplitView {
View1()
View2()
.frame(minWidth: 500)
}
Embedding one split view inside other may work.
Some problems arise if using GeometryReader.
HSplitView
{
View1()
HSplitView
{
View2()
View3()
}
}

Span two Buttons across HStack in SwiftUI (Big Sur)

I have a problem getting the correct alignment for two buttons (Cancel and OK) in a sheet using SwiftUI.
I want the two buttons to appear at the bottom of the sheet so that the two of them span the whole horizontal width of the sheet (minus padding).
I've found several answers (such as setting the maxWidth to .infinity or by putting the text contend of a button in a separate Text view and surrounding it by Spacers) but none of them seem to work for me. The only one that works somewhat is by creating my own ButtonStyle. But then I have to recreate the whole default ButtonStyle (different for default button, for day and night mode,...)
The code I have now is:
var body: some View {
VStack() {
...
HStack {
Button("Cancel",action: {
isPresented.toggle()
})
.frame(minWidth: 0, maxWidth: .infinity)
.padding()
.keyboardShortcut(.cancelAction)
.border(Color.red)
Button("OK", action: {
isPresented.toggle()
let newServer = Server(name: name, url: url, port: port, autoConnect: autoConnect)
do {
try model.add(server: newServer)
} catch {
print("Could not add server.")
}
})
.frame(minWidth: 0, maxWidth: .infinity)
.padding()
.keyboardShortcut(.defaultAction)
.border(Color.red)
}
.frame(maxWidth: .infinity)
.padding(.top, 20)
.border(Color.blue)
}
.padding()
.frame(minWidth: 300, maxWidth: 300)
}
This results in the following sheet:
I would like both buttons to fill the area surrounded by the red border.
I'm kinda at a loss on what to try next!
as the red borders show, the buttons do extend to fill the area.
To get the background gray as in the picture, you could use something like:
.background(Color(UIColor.systemGray4))
as the last modifier of each buttons.
Default button style is rendered around provided label content, so possible solution is to calculate needed width for button label dynamically and apply it explicitly.
Note: please also see my comment for alternate
Demo prepared with Xcode 12.5 / macOS 11.3
struct ContentView: View {
#State private var cancelWidth = CGFloat.zero
#State private var okWidth = CGFloat.zero
var body: some View {
VStack() {
Text("Demo here!")
HStack(spacing: 0) {
Button(action: {
}) { Text("Cancel").frame(width: cancelWidth) }
.frame(minWidth: 0, maxWidth: .infinity)
.background(GeometryReader {
Color.clear.preference(key: ViewWidthKey.self,
value: $0.frame(in: .local).size.width) })
.onPreferenceChange(ViewWidthKey.self) { self.cancelWidth = $0 }
.padding()
.border(Color.red)
.keyboardShortcut(.cancelAction)
Button(action: {
}) { Text("OK").frame(width: okWidth) }
.frame(minWidth: 0, maxWidth: .infinity)
.background(GeometryReader {
Color.clear.preference(key: ViewWidthKey.self,
value: $0.frame(in: .local).size.width) })
.onPreferenceChange(ViewWidthKey.self) { self.okWidth = $0 }
.padding()
.keyboardShortcut(.defaultAction)
.border(Color.red)
}
.frame(maxWidth: .infinity)
.padding(.top, 20)
.border(Color.blue)
}
.padding()
.frame(minWidth: 300, maxWidth: 300)
}
}
struct ViewWidthKey: PreferenceKey {
typealias Value = CGFloat
static var defaultValue: CGFloat { 0 }
static func reduce(value: inout Value, nextValue: () -> Value) {
value = value + nextValue()
}
}

I'm stumped how to pop a subview over a view with SwiftUI

I'm working on an app for iPad that is to be used to manage a personal cash budget. I'm working with XCode 12 building for iOS 14 using SwiftUI. I have most of the underlying MODEL work done, but I'm struggling with the UI. I've programmed in various languages since 1979. (Yes I' old and doing this is a hobby :-). I can not for the life of me figure out the technique to pop an edit/entry view over a parent view. As an example, I have an app the I also use that does just this. I'm attaching an image that shows what I'd like to be able to do. I've tried .overlay() and ZStack, but what I've tried just doesn't give me what I want. If you could look at the image I'm posting and point me in the right direction for even just for technique I'd be really appreciative....
Image of entry view popped over subview:
Image of subview before:
The image looks like the new view is being presented via the .sheet() modifier. This is the most common approach to present a view like this. I would just note that it looks slightly different on iPad (like the image above) vs iPhone (extends full width of screen).
struct Test: View {
#State var showSheet: Bool = false
var body: some View {
Text("Hello, World!")
.onTapGesture {
showSheet.toggle()
}
.sheet(isPresented: $showSheet, content: {
Text("Next view")
})
}
}
Alternatively, here are 2 other methods to present a sheet, which can be a little more customizable (custom animations/transitions) if required.
struct Test2: View {
#State var showSheet: Bool = false
var body: some View {
ZStack {
Text("Hello, World!")
.frame(maxWidth: .infinity, maxHeight: .infinity)
.onTapGesture {
showSheet.toggle()
}
if showSheet {
Text("Next view")
.frame(maxWidth: .infinity, maxHeight: .infinity)
.background(Color.red)
.animation(.spring())
.transition(.move(edge: .bottom))
}
}
}
}
struct Test3: View {
#State var showSheet: Bool = false
var body: some View {
ZStack {
Text("Hello, World!")
.frame(maxWidth: .infinity, maxHeight: .infinity)
.onTapGesture {
showSheet.toggle()
}
Text("Next view")
.frame(maxWidth: .infinity, maxHeight: .infinity)
.background(Color.red)
.opacity(showSheet ? 1.0 : 0)
.animation(.spring())
}
}
}

How to turn off NavigationLink overlay color in SwiftUI?

I've designed a "CardView" using ZStack in which the background layer is a gradient and the foreground layer is a PNG(or PDF) image(the image is a yellow path(like a circle) drawn in Adobe Illustrator).
When I put the ZStack inside a NavigationLink the gradient remains unchanged and fine, but the image get a bluish overlay color (like default color of a button) therefore there is no more yellow path(the path is bluish).
How can get the original color of foreground PNG(or PDF) image?
import SwiftUI
struct MyCardView : View {
let cRadius : CGFloat = 35
let cHeight : CGFloat = 220
var body: some View {
NavigationView {
NavigationLink(destination: Text("Hello")) {
ZStack {
RoundedRectangle(cornerRadius: cRadius)
.foregroundColor(.white)
.opacity(0)
.background(LinearGradient(gradient: Gradient(colors: [Color(red: 109/255, green: 58/255, blue: 242/255),Color(red: 57/255, green: 23/255, blue: 189/255)]), startPoint: .leading, endPoint: .trailing), cornerRadius: 0)
.cornerRadius(cRadius)
.frame(height: cHeight)
.padding()
Image("someColoredPathPNGimage")
}
}
}
}
}
The navigationLink acts like Button and it gets the default button style with blue color.
Using .renderingMode(.original) only works on Image views. What if you decide to load your image using some libs or pods?!
It is better to change the default button style to plain using the modifier below:
NavigationLink(destination: Text("Hello")) {
ZStack {
RoundedRectangle(cornerRadius: cRadius)
.foregroundColor(.white)
.opacity(0)
.background(LinearGradient(gradient: Gradient(colors: [Color(red: 109/255, green: 58/255, blue: 242/255),Color(red: 57/255, green: 23/255, blue: 189/255)]), startPoint: .leading, endPoint: .trailing), cornerRadius: 0)
.cornerRadius(cRadius)
.frame(height: cHeight)
.padding()
Image("someColoredPathPNGimage")
}
}
.buttonStyle(PlainButtonStyle()) // Here is what you need
Add .buttonStyle(PlainButtonStyle()) to the NavigationLink(....)
NavigationLink(
destination: Text("Destination"),
label: {
Text("Click Here!")
}
)
.buttonStyle(PlainButtonStyle())
Try:
Image("someColoredPathPNGimage").renderingMode(.original)
If your problems continue, consider uploading a screenshot so we get an idea of what you mean. If you can include the image you are using, even better, so we can replicate.
You change colour with accentColor modifier
NavigationLink(
destination: Text("Destination"),
label: {
Text("Click Here!")
}
)
.accentColor(Color.black)

Resources