I've stumbled across what looks like a bug caused by a NavigationView.
For the reproducible code here we have two circles that pulsate . It works fine, until we add the NavigationView ! What is going on? I'm pretty sure this is a bug, but is it well known, any workarounds?
GLIMPSE OF GLITCHING ANIMATION
REPRODUCIBLE CODE :
import SwiftUI
struct ContentView: View {
#State private var animationAmount: CGFloat = 1
var body: some View {
NavigationView {
HStack {
Circle()
.frame(width: 100, height: 100)
.overlay (
Circle()
.stroke()
.scaleEffect(animationAmount)
.opacity(Double(2 - animationAmount))
.animation(
Animation.easeInOut(duration: 1).repeatForever(autoreverses: false)
)
).onAppear {
self.animationAmount = 2
}
Circle()
.frame(width: 100, height: 100)
.overlay (
Circle()
.stroke()
.scaleEffect(animationAmount)
.opacity(Double(2 - animationAmount))
.animation(
Animation.easeInOut(duration: 1).repeatForever(autoreverses: false)
))
}
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
Related
I want to implement the animation like AppStore. Drag down detail page to scale down and remove itself view.
But when I started to drag, the console keeps printing var scale is changing and app is freezes. Looks like go into infinity loop. Here is my code:
import SwiftUI
struct TestView: View {
#State var showDetail = false
var body: some View {
ZStack {
Text("First View")
.frame(maxWidth: .infinity, maxHeight: .infinity)
.background(Color.accentColor)
.onTapGesture {
withAnimation {
showDetail = true
}
}
if showDetail {
TestDetailView(showDetail: $showDetail)
}
}
.ignoresSafeArea()
}
}
struct TestDetailView: View {
#State var scale: CGFloat = 1 {
didSet {
print("scale: \(scale)")
}
}
#Binding var showDetail: Bool
var body: some View {
ScrollView {
VStack(spacing: 0) {
Text("Upper Part")
.foregroundColor(.white)
.frame(maxWidth: .infinity, maxHeight: .infinity)
.background(Color.gray)
.gesture(DragGesture(minimumDistance: 0)
.onChanged { val in
let s = val.translation.height / UIScreen.main.bounds.height
if s > 0 && 1 - s > 0.7 {
scale = 1 - s
if scale < 0.8 {
withAnimation {
showDetail = false
}
}
}
}
)
Text("Lower Part")
.foregroundColor(.white)
.frame(maxWidth: .infinity, maxHeight: .infinity)
.background(.primary)
.onTapGesture {
withAnimation(.easeInOut) {
showDetail = false
}
}
}
.frame(height: UIScreen.main.bounds.height)
}
.scaleEffect(scale)
.animation(.easeInOut, value: showDetail)
}
}
struct TestView_Previews: PreviewProvider {
static var previews: some View {
TestView()
}
}
I know it's relative to the drag gesture and setting scale but I don't know how to fix that.
The images above is what I want to implement, my code simplified
the layout, you may try to drag down the upper part, then you'll know what happen.
So in the past I have been tracking the scroll position using a scroll view but I've fallen into a situation where I need to track the position using a List. I am using a List because I want some of the built in real estate to create my views such as the default List styles.
I can get the value using PreferenceKeys, but the issue is when I scroll to far upwards, the PreferenceKey value will default back to its position 0, breaking my show shy header view logic.
This is the TrackableListView code
struct TrackableListView<Content: View>: View {
let offsetChanged: (CGPoint) -> Void
let content: Content
init(offsetChanged: #escaping (CGPoint) -> Void = { _ in }, #ViewBuilder content: () -> Content) {
self.offsetChanged = offsetChanged
self.content = content()
}
var body: some View {
List {
GeometryReader { geometry in
Color.clear.preference(key: ScrollOffsetPreferenceKey.self, value: geometry.frame(in: .named("ListView")).origin)
}
.frame(width: 0, height: 0)
content
.offset(y: -10)
}
.coordinateSpace(name: "ListView")
.onPreferenceChange(ScrollOffsetPreferenceKey.self, perform: offsetChanged)
}
}
private struct ScrollOffsetPreferenceKey: PreferenceKey {
static var defaultValue: CGPoint = .zero
static func reduce(value: inout CGPoint, nextValue: () -> CGPoint) { }
}
And this is my ContentView:
struct ContentView: View {
#State private var contentOffset = CGFloat(0)
#State private var offsetPositionValue: CGFloat = 0
#State private var isShyHeaderVisible = false
var body: some View {
NavigationView {
ZStack {
TrackableListView { offset in
withAnimation {
contentOffset = offset.y
}
} content: {
Text("\(contentOffset)")
}
.overlay(
ZStack {
HStack {
Text("Total points")
.foregroundColor(.white)
.lineLimit(1)
Spacer()
Text("20,000 pts")
.foregroundColor(.white)
.padding(.leading, 50)
}
.padding(.horizontal)
.padding(.vertical, 8)
.frame(width: UIScreen.main.bounds.width)
.background(Color.green)
.offset(y: contentOffset < 50 ? 0 : -5)
.opacity(contentOffset < 50 ? 1 : 0)
.transition(.move(edge: .top))
}
.frame(maxHeight: .infinity, alignment: .top)
)
}
.navigationTitle("Hello")
.navigationViewStyle(StackNavigationViewStyle())
.navigationBarTitleDisplayMode(.inline)
.frame(maxHeight: .infinity, alignment: .top)
.background(AccountBackground())
}
}
}
The issue was that Other Views in my hierarchy (probably introduced by SwiftUI) could be sending the default value, which is why you start getting zero sometimes.
What I needed was a way to determine when to use or forward the new value, versus when to ignore it.
To do this I had to make my value optional GCPoint, with a nil default value, then in your reduce method when using PreferenceKeys you have to do:
if let nextValue = nextValue() {
value = nextValue
}
Then make sure your CGPoint is an optional value.
I am using the new #FocusState to control how my views react to the user deciding to start inputting information into text fields. My current need is to wrap an animation around my top view leaving the screen as the keyboard moves up. Usually this kind of thing can be accomplished by simply wrapping withAnimation() around a boolean toggle, but since Swift is toggling my focus state bool under the hood, I can't wrap an animation around it in this way. How else should I do it?
Here is a minimal reproducible example. Basically I want to animate the top (red) view leaving / coming back into view with changes to my focus state isFocused var.
struct ContentView: View {
#State var text: String = ""
#FocusState var isFocused: Bool
var body: some View {
ZStack {
VStack {
if !isFocused {
Text("How to Animate this?")
.frame(width: 300, height: 300)
.background(Color.red)
.animation(.easeInOut(duration: 5), value: isFocused)
}
Text("Middle Section")
.frame(width: 300, height: 300)
.background(Color.green)
Spacer()
TextField("placeholder", text: $text)
.focused($isFocused)
}
if isFocused {
Color.white.opacity(0.1)
.onTapGesture {
isFocused = false
}
}
}
}
}
I don't think the animation modifier that's currently on the top view is doing anything, but I imagine that that's where I'll put some animation code.
Here is something that works. I've done this before to make an animation happen upon an #FocusState property changing its value. Can't really tell you why though, it's just something I figured out with trial and error.
struct ContentView: View {
#State var text: String = ""
#FocusState var isFocused: Bool
#State private var showRedView = false
var body: some View {
ZStack {
VStack {
if !showRedView {
Text("How to Animate this?")
.frame(width: 300, height: 300)
.background(Color.red)
}
Text("Middle Section")
.frame(width: 300, height: 300)
.background(Color.green)
Spacer()
TextField("placeholder", text: $text)
.focused($isFocused)
}
.onChange(of: isFocused) { bool in
withAnimation(.easeInOut(duration: 5)) {
showRedView = bool
}
}
if isFocused {
Color.white.opacity(0.1)
.onTapGesture {
isFocused = false
}
}
}
}
}
I am trying to load image from server in a slide show with SwiftUi but image is not loading in the slide show. please see my code below
import SwiftUI
import Combine
struct ImageCarouselView<Content: View>: View {
private var numberOfImages: Int
private var content: Content
#State private var currentIndex: Int = 0
private let timer = Timer.publish(every: 3, on: .main, in: .common).autoconnect()
init(numberOfImages: Int, #ViewBuilder content: () -> Content) {
self.numberOfImages = numberOfImages
self.content = content()
}
var body: some View {
GeometryReader { geometry in
// 1
ZStack(alignment: .bottom) {
HStack(spacing: 0) {
self.content
}
.frame(width: geometry.size.width, height: geometry.size.height, alignment: .leading)
.offset(x: CGFloat(self.currentIndex) * -geometry.size.width, y: 0)
.animation(.spring())
.onReceive(self.timer) { _ in
self.currentIndex = (self.currentIndex + 1) % (self.numberOfImages == 0 ? 1 : self.numberOfImages)
}
// 2
HStack(spacing: 3) {
// 3
ForEach(0..<self.numberOfImages, id: \.self) { index in
// 4
Circle()
.frame(width: index == self.currentIndex ? 10 : 8,
height: index == self.currentIndex ? 10 : 8)
.foregroundColor(index == self.currentIndex ? Color.blue : .white)
.overlay(Circle().stroke(Color.gray, lineWidth: 1))
.padding(.bottom, 8)
.animation(.spring())
}
}
}
}
}
}
and in my view
struct MainView: View {
#Binding var showMenu: Bool
#State var index = 0
var body: some View {
ScrollView {
//MainView(with:"https://govcard.net/banners/banner1.jpg")
GeometryReader { geometry in
ImageCarouselView(numberOfImages: 3) {
Image("image_carousel_1")
.resizable()
.scaledToFill()
.frame(width: geometry.size.width, height: geometry.size.height)
.clipped()
Image("image_carousel_2")
.resizable()
.scaledToFill()
.frame(width: geometry.size.width, height: geometry.size.height)
.clipped()
Image("image_carousel_3")
.resizable()
.scaledToFill()
.frame(width: geometry.size.width, height: geometry.size.height)
.clipped()
}
}.frame(height: 200, alignment: .center)
}
i want to use url instead of asset image.
As far as I've seen the easiest way would be to add the Kingfisher Swift package to your project.import KingfisherSwiftUI to your swift file and use the KFImage(myUrl).
import KingfisherSwiftUI
struct MainView: View {
//...
KFImage(URL(string: "https://example.com/image.png")!)
}
Here are the steps to add the Kingfisher to your project:
Select File > Swift Packages > Add Package Dependency.
Enter https://github.com/onevcat/Kingfisher.git in the "Choose Package Repository" dialog.
In the next page, specify the version resolving rule as "Up to Next Major" with "5.14.0" as its earliest version.
I'm beginning to try out SwiftUI. I want the Circle is randomly changing the color from red or green.
struct ContentView: View {
#State var colorCircle = [".red", ".green"]
var body: some View {
ZStack {
Circle()
.fill(Color.colorCircle.randomElement())
.frame(width: 100,
height: 100)
.position(x: 250,
y: 320)
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
First make your colorCircle array to contain Colors and not Strings.
#State var colorCircle: [Color] = [.red, .green]
Also note you're trying to access colorCircle from inside the Color struct:
Color.colorCircle.randomElement()
As you declared colorCircle in the ContentView you can use it like:
self.colorCircle.randomElement()
or (shorter):
colorCircle.randomElement()
Then you can use randomElement:
struct ContentView: View {
#State var colorCircle: [Color] = [.red, .green]
var body: some View {
ZStack {
Circle()
.fill(colorCircle.randomElement()!)
.frame(width: 100,
height: 100)
.position(x: 250,
y: 320)
}
}
}
Note that randomElement() returns an optional so you can force-unwrap it (!) if you're sure your array is not empty.
However, as a good practice, I recommend not to use ! and provide default values if possible:
.fill(colorCircle.randomElement() ?? .red)