How can I change ObservedObject - swift

*DataStoreCode:
How can I assign a variable to the parameter in the getTrailer function ?
I can't send data to the parameter when I click and the link doesn't work
class DataStore: ObservableObject {
#Published var movie: MovieRoot?
#Published var trailer: TrailerRoot?
#Published var url: String?
init() {
self.getMovies()
self.getTrailers(url: url ?? "")
}
func getMovies() {
JSONService().getMovies(url: "https://api.themoviedb.org/3/movie/upcoming?api_key=594b8eb4999a812345136ee3ed1ebdb&language=tr-TR&page=1", model: movie) { (result) in
self.movie = result
}
}
func getTrailers(url: String) { //This parameter.
JSONService().getMovies(url: url, model: trailer) { (result) in
self.trailer = result
}
}
}
My ContentView Codes:
ForEach(store.movie?.results ?? []) { item in
VStack()
.onTapGesture {
self.show.toggle()
self.store.url = "https://api.themoviedb.org/3/movie/\(item.id)/videos?api_key=594b8eb499123456ad5136ee3ed1ebdb&language=en-US"
print(self.store.trailer?.results)
}
}
JSON Service
class JSONService {
func getMovies<T: Decodable>(url: String, model: T, completion: #escaping (T) -> ()) {
guard let url = URL(string: url) else { return }
URLSession.shared.dataTask(with: url) { (data, response, error) in
guard let data = data else { return }
do {
let items = try JSONDecoder().decode(T.self, from: data)
DispatchQueue.main.async {
completion(items)
}
} catch let error {
print(error)
}
}
.resume()
}
}
ContentView
struct ContentView: View {
#State var show: Bool
//#State var movie: MovieRoot
#ObservedObject var store = DataStore()
#State var selectedMovie: Movie
#State var id: Int
var imageURL = "https://image.tmdb.org/t/p/w185_and_h278_bestv2"
//var trailerURL = "https://api.themoviedb.org/3/movie/338762/videos?api_key=594b8eb4999a8b44ad5136ee3ed1ebdb&language=en-US"
var body: some View {
ZStack {
ScrollView(.horizontal, showsIndicators: false) {
HStack {
ForEach(store.movie?.results ?? []) { item in
GeometryReader { geometry in
VStack {
//Image("film")
WebImage(url: URL(string: self.imageURL + item.poster_path))
.resizable()
.retryOnAppear(true)
.onSuccess()
.renderingMode(.original)
.cornerRadius(30)
.frame(width: 185, height: 278)
.shadow(color: Color.black.opacity(0.4), radius: 10, x: 0, y: 10)
.animation(.spring(response: 0.5, dampingFraction: 0.6, blendDuration: 0))
Text(item.title)
.font(.system(size: 20, weight: .bold))
CircleView(firstColor: #colorLiteral(red: 0.2392156869, green: 0.6745098233, blue: 0.9686274529, alpha: 1), secondColor: #colorLiteral(red: 0.8078431487, green: 0.02745098062, blue: 0.3333333433, alpha: 1), width: 50, height: 50, percent: item.vote_average)
}
.rotation3DEffect(Angle(degrees: Double(geometry.frame(in: .global).minX - 30) / -20), axis: (x: 0, y: 10.0, z: 0))
.frame(width: UIScreen.main.bounds.width)
.onTapGesture {
self.show.toggle()
Here-> self.store.url = "https://api.themoviedb.org/3/movie/\(item.id)/videos?api_key=594b8eb4999a8b44ad5136ee3ed1ebdb&language=en-US"
print(self.store.trailer?.results)
}
.offset(y: self.show ? .zero : -UIScreen.main.bounds.height)
.animation(.easeIn)
}
.frame(width: UIScreen.main.bounds.width)
}
}
.frame(maxHeight: .infinity)
}
DetailView(show: $show, movie: $selectedMovie)
}
// .onAppear {
// JSONService().getMovie(url: "https://api.themoviedb.org/3/movie/upcoming?api_key=594b8eb4999a8b44ad5136ee3ed1ebdb&language=tr-TR&page=1") { (result) in
// self.movie.results = result.results
// }
// }
}
}
How can I check that the url variable in the DataSource class has changed. I'm throwing item.id into the url variable, but it doesn't work.
I redirect the data in Content View to the Detail View page.
DetailView
struct DetailView: View {
#ObservedObject var trailer = DataStore()
#Binding var show: Bool
#Binding var movie: Movie
var body: some View {
ScrollView(.vertical, showsIndicators: false) {
VStack {
Image(systemName: "multiply.circle")
.imageScale(.large)
.padding()
.onTapGesture {
self.show.toggle()
}
.onDisappear()
WebView(request: URLRequest(url: URL(string: "https://www.youtube.com/watch?v=gn5QmllRCn4")!))
.frame(height: UIScreen.main.bounds.height * 0.3)
.background(Color(UIColor.secondarySystemBackground))
.cornerRadius(20)
.shadow(color: Color.black.opacity(0.3), radius: 10, x: 0, y: 10)
HStack {
Text(movie.title)
.font(.system(size: 20, weight: .semibold))
Spacer()
CircleView(firstColor: #colorLiteral(red: 0.2196078449, green: 0.007843137719, blue: 0.8549019694, alpha: 1), secondColor: #colorLiteral(red: 0.8078431487, green: 0.02745098062, blue: 0.3333333433, alpha: 1), width: 30, height: 30, percent: 4.6)
}
.padding(.horizontal)
.background(Color(UIColor.secondarySystemBackground))
.cornerRadius(10)
.shadow(color: Color.black.opacity(0.3), radius: 5, x: 0, y: 5)
.padding()
Text(movie.overview)
.padding()
.background(Color(UIColor.secondarySystemBackground))
.cornerRadius(20)
.padding(.horizontal)
.shadow(color: Color.black.opacity(0.3), radius: 10, x: 0, y: 10)
}
}
.animation(.easeIn)
.offset(y: show ? UIScreen.main.bounds.height : .zero)
.onReceive(trailer.$url) { (_) in
}
}
}

Related

Value of type 'ObservedObject<TimerStructManager>.Wrapper' has no dynamic member 'exerciseTime' using key path from root type 'TimerStructManager'

I am making an exercise app and I want to implement persistence so that users cant rack how much exercise they have gotten done for the day and I want that data to persist.
I created a manager to manage the data from the TimerStruct, which is where the total time for exercise comes from but when I try to implment it into my home screen view, I keep getting this error.
Value of type 'ObservedObject<TimerStructManager>.Wrapper' has no dynamic member 'exerciseTime' using key path from root type 'TimerStructManager' and Cannot convert value of type 'Binding<Subject>' to expected argument type 'Int' at this line.
let percent = Double($timer.exerciseTime/1500)
TimerStruct:
import Foundation
import SwiftUI
struct TimerStruct: Codable, Identifiable {
var id = UUID()
var exerciseTime = 0.0
var isAlertpresented = false
var countdownTimer = 300
var timerRunning = false
var isPaused = false
var isActive = false
var isCompleted = false
}
TimerManager:
import Foundation
import SwiftUI
class TimerStructManager: ObservableObject {
#Published var timerStructs: [TimerStruct] = [] {
didSet {
save()
}
}
let sampleTimerStructs: [TimerStruct] = []
init() {
load()
}
func getArchiveURL() -> URL {
let plistName = "timerStructs.plist"
let documentsDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
return documentsDirectory.appendingPathComponent(plistName)
}
func save() {
let archiveURL = getArchiveURL()
let propertyListEncoder = PropertyListEncoder()
let encodedTimerStructs = try? propertyListEncoder.encode(timerStructs)
try? encodedTimerStructs?.write(to: archiveURL, options: .noFileProtection)
}
func load() {
let archiveURL = getArchiveURL()
let propertyListDecoder = PropertyListDecoder()
var finalTimerStructs: [TimerStruct]!
if let retrievedTimerStructData = try? Data(contentsOf: archiveURL),
let decodedTimerStructs = try? propertyListDecoder.decode([TimerStruct].self, from: retrievedTimerStructData) {
finalTimerStructs = decodedTimerStructs
} else {
finalTimerStructs = sampleTimerStructs
}
timerStructs = finalTimerStructs
}
}
HomeScreen
import AVKit
import SwiftUI
struct HomeView: View {
#ObservedObject var timer: TimerStructManager
#Binding var streak: Streaks
#Binding var timerStruct: TimerStruct
#State var isSheetPresented = false
#Binding var navigationPath: NavigationPath
#Binding var exercisePlans: [ExercisePlan]
func ridzero(result: Double) -> String {
let value = String(format: "%g", result)
return value
}
func round(result: Double) -> String {
let value = String(format: "%.1f", result)
return value
}
var body: some View {
//NavigationView {
GeometryReader { geometry in
ScrollView {
ZStack {
VStack {
let percent = Double($timer.exerciseTime/1500)
Text("Welcome back to ElderlyFit")
.font(.system(size: 25,weight: .medium, design: .rounded))
.offset(x: 0, y: 20)
CircularProgressView(timer: $timerStruct, progress: CGFloat(percent))
.frame(width: 150, height: 150)
.offset(x: -95, y: -240)
.padding(EdgeInsets(top: 280, leading: 0, bottom: 0, trailing: 0))
Text("\(round(result:percent*100))%")
.font(.system(size: 30, weight: .bold, design: .rounded))
.offset(x:-92, y:-345)
Text("\(round(result: $timer.exerciseTime/60)) mins of exercise completed today")
.frame(width: 200, height: 50)
.font(.system(size: 20, design: .rounded))
.offset(x:100, y:-440)
Button {
navigationPath.append("ExercisePlanDetailView")
} label: {
Text("Start exercise")
}
.padding()
.background((Color(red: 184/255, green: 243/255, blue: 255/255)))
.foregroundColor(.black)
.cornerRadius(10)
.offset(x: 92, y: -430)
.font(Font.system(size: UIFontMetrics.default.scaledValue(for: 16)))
StreaksView(timer: $timerStruct, streak: $streak)
.offset(x:0, y: -370)
.padding()
Text("Choose your exercise plan:")
.bold()
.font(.system(size: 25))
.offset(x: -30, y: -450)
.zIndex(1.0)
ExercisePlanView( streaks: $streak, timer: $timerStruct, navigationPath: $navigationPath, exercisePlans: $exercisePlans)
.offset(x: 15, y: -430)
.zIndex(-1.0)
.font(Font.system(size: UIFontMetrics.default.scaledValue(for: 15)))
}
.frame(width: geometry.size.width)
.edgesIgnoringSafeArea(.all)
}
// .background(Color("appBackground"))
// .edgesIgnoringSafeArea(.all)
}
}
}
//.navigationTitle("Home")
}

self.presentationMode.wrappedValue.dismiss() not working in SwiftUI

Here is the code of login page, navigate from tabview using navigation-link
and here I want to pop back once get the api response,
Also if any way to pop back using navigationLink same link push, please let me know.
thanks in advance!!
struct LoginBody: View {
#ObservedObject var viewModel = LoginViewModel()
#State private var email: String = ""
#State private var password: String = ""
#Environment(\.presentationMode) private var presentationMode: Binding<PresentationMode>
var loginButton: some View {
Button(action: {
DispatchQueue.main.async {
print("Email:- ", email)
print("Password:- ", password)
viewModel.loginAPI(email: email, password: password)
DispatchQueue.main.asyncAfter(deadline: .now() + 8, execute: {
print("presentationMode dismiss")
self.presentationMode.wrappedValue.dismiss()
})
}
}) {
HStack(alignment: .center) {
Spacer()
Text("Login")
.font(.system(size: 20).bold())
.frame(alignment: .center)
Spacer()
}
}
.padding(EdgeInsets(top: 15, leading: 20, bottom: 15, trailing: 20))
.background(Color(#colorLiteral(red: 0.3340760444, green: 0.764342023, blue: 0.8197288968, alpha: 1)))
.foregroundColor(Color.white)
.cornerRadius(5)
.shadow(color: Color.init(red: 0.0, green: 0.0, blue: 0.0, opacity: 0.4), radius: 2, x: 0, y: 2)
.frame(height: 40, alignment: .center)
}
}
don't know how you get this to compile, but rename "loginButton" to "body".

SwiftUI removing item from Form causes crash [duplicate]

As you can see from the image I have a list of colors, I would like to be able to also give the possibility to delete a color from the array.
I tried to add a list and then call the onDelete call on ForEach, but it's not working well it gives me problems.
Then in addition to this I would like the list to be the size of the contained elements.
Error:
Fatal error: Index out of range: file Swift/ContiguousArrayBuffer.swift, line 444
Can anyone give me some advice?
Code:
import SwiftUI
struct ContentView: View {
var cornerRadius: CGFloat = 16
#State public var select = 2
#State public var bgColors: [Color] =
[
Color(red: 21.0/255.0, green: 101.0/255.0, blue: 192.0/255.0),
Color(red: 255.0/255.0, green: 193.0/255.0, blue: 7.0/255.0),
Color(red: 76.0/255.0, green: 175.0/255.0, blue: 80.0/255.0)
]
#Environment(\.colorScheme) var colorScheme
#State var isShowPicker: Bool = false
#State var image: Image? = Image("placeholder")
#State private var url: String = "https://a.wattpad.com/useravatar/climaxmite.256.718018.jpg"
init() {
// Segmented control colors
UISegmentedControl.appearance().backgroundColor = .systemGray6
UISegmentedControl.appearance().selectedSegmentTintColor = UIColor(Color.blue)
UISegmentedControl.appearance().setTitleTextAttributes([.foregroundColor: UIColor.systemBackground], for: .selected)
UISegmentedControl.appearance().setTitleTextAttributes([.foregroundColor: UIColor.label], for: .normal)
}
var body: some View {
VStack{
ZStack {
RoundedRectangle(cornerRadius: cornerRadius)
.frame(width: UIScreen.main.bounds.width-40, height: 100, alignment: .center)
.foregroundColor(colorScheme == .dark ? .black : .white)
VStack(spacing: 12) {
ZStack {
Rectangle()
.frame(width: UIScreen.main.bounds.width-47, height: 35, alignment: .center)
.foregroundColor(Color(UIColor.systemGray6))
.cornerRadius(cornerRadius, corners: [.topLeft, .topRight])
Text("Select Background")
.foregroundColor(Color(UIColor.label))
.font(.subheadline)
.bold()
}
Picker(selection: $select, label: Text("Select Background")) {
Text("Url").tag(0)
Text("Select Image").tag(1)
Text("Gradient").tag(2)
}.pickerStyle(SegmentedPickerStyle())
.padding(EdgeInsets(top: 0, leading: 30, bottom: 0, trailing: 30))
Spacer()
.frame(height: 3)
}
}
if self.select == 0 {
VStack{
ZStack {
RoundedRectangle(cornerRadius: cornerRadius)
.frame(width: UIScreen.main.bounds.width-40, height: 42, alignment: .center)
.foregroundColor(Color(UIColor.systemBackground))
TextField("http://", text: $url)
.padding(10)
.frame(width: UIScreen.main.bounds.width-40)
.foregroundColor(Color(UIColor.label))
.cornerRadius(cornerRadius)
.padding(EdgeInsets(top: 10, leading: 20, bottom: 10, trailing: 10))
}
Button(action: {
}, label: {
Text("Submit")
.foregroundColor(Color(UIColor.systemBackground))
.bold()
})
.padding(EdgeInsets(top: 10, leading: 20, bottom: 10, trailing: 20))
.foregroundColor(.white)
.font(.subheadline)
.background(Color.blue)
.cornerRadius(cornerRadius)
}
}
if self.select == 1 {
VStack {
Button(action: {
withAnimation {
self.isShowPicker.toggle()
}
}) {
Image(systemName: "photo")
.font(.headline)
.foregroundColor(colorScheme == .dark ? .white : .black)
Text("Import")
.font(.headline)
.foregroundColor(colorScheme == .dark ? .white : .black)
}
.foregroundColor(.black)
}
.sheet(isPresented: $isShowPicker) {
ImagePicker(image: self.$image)
}
}
if self.select == 2 {
VStack(alignment: .trailing){
Button(action: {
bgColors.append(Color.clear)
}) {
Image(systemName: "plus")
.font(.headline)
.foregroundColor(colorScheme == .dark ? .white : .black)
.padding(EdgeInsets(top: 10, leading: 20, bottom: 10, trailing: 15))
}
List {
ForEach(Array(bgColors.enumerated()), id: \.offset) { index, element in
ZStack {
ColorPicker("Set the background color", selection: $bgColors[index])
}
.padding(EdgeInsets(top: 10, leading: 20, bottom: 10, trailing: 10))
} .onDelete(perform: delete)
}.background(Color.blue)
}
}
Spacer()
}
.padding(.top, 25)
.ignoresSafeArea(.keyboard)
.background(Color(UIColor.systemGray6))
.edgesIgnoringSafeArea(.all)
}
func delete(at offsets: IndexSet) {
bgColors.remove(atOffsets: offsets)
}
}
struct RoundedCorner: Shape {
var radius: CGFloat = .infinity
var corners: UIRectCorner = .allCorners
func path(in rect: CGRect) -> Path {
let path = UIBezierPath(roundedRect: rect, byRoundingCorners: corners, cornerRadii: CGSize(width: radius, height: radius))
return Path(path.cgPath)
}
}
extension View {
func cornerRadius(_ radius: CGFloat, corners: UIRectCorner) -> some View {
clipShape( RoundedCorner(radius: radius, corners: corners) )
}
}
// extension for keyboard to dismiss
extension UIApplication {
func endEditing() {
sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil)
}
}
struct ImagePicker: UIViewControllerRepresentable {
#Environment(\.presentationMode)
var presentationMode
#Binding var image: Image?
class Coordinator: NSObject, UINavigationControllerDelegate, UIImagePickerControllerDelegate {
#Binding var presentationMode: PresentationMode
#Binding var image: Image?
init(presentationMode: Binding<PresentationMode>, image: Binding<Image?>) {
_presentationMode = presentationMode
_image = image
}
func imagePickerController(_ picker: UIImagePickerController,
didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
let uiImage = info[UIImagePickerController.InfoKey.originalImage] as! UIImage
image = Image(uiImage: uiImage)
presentationMode.dismiss()
}
func imagePickerControllerDidCancel(_ picker: UIImagePickerController) {
presentationMode.dismiss()
}
}
func makeCoordinator() -> Coordinator {
return Coordinator(presentationMode: presentationMode, image: $image)
}
func makeUIViewController(context: UIViewControllerRepresentableContext<ImagePicker>) -> UIImagePickerController {
let picker = UIImagePickerController()
picker.delegate = context.coordinator
return picker
}
func updateUIViewController(_ uiViewController: UIImagePickerController,
context: UIViewControllerRepresentableContext<ImagePicker>) {
}
}
The problem is that in your List, the id you give it is \.offset. However, since you are removing data from bgColors, so this data can change. Instead, you should set the id as \.element because it will be constant for each color.
Consider this simplified example, which crashes when you remove a Color from the list:
struct ContentView: View {
#State private var arr: [Color] = [.red, .green, .blue]
var body: some View {
List {
ForEach(Array(arr.enumerated()), id: \.offset) { (index, _) in
ColorPicker("Color", selection: $arr[index])
}
.onDelete(perform: delete)
}
}
private func delete(at offsets: IndexSet) {
arr.remove(atOffsets: offsets)
}
}
And the working example, where the changes are the id given to the List, and the new Binding to the color (note the custom Binding for the selection):
struct ContentView: View {
#State private var arr: [Color] = [.red, .green, .blue]
var body: some View {
List {
ForEach(Array(arr.enumerated()), id: \.element) { (index, _) in
ColorPicker(
"Color",
selection: Binding<Color>(
get: { arr[index] },
set: { arr[index] = $0 }
)
)
}
.onDelete(perform: delete)
}
}
private func delete(at offsets: IndexSet) {
arr.remove(atOffsets: offsets)
}
}

How do I show a new view after some delay amount of time SwiftUI

So I am building a blackjack simulator game and my problem right now is making it more realistic. From when I press the "Hit Me" button I'd like a delay before my next card is uploaded. Similarily when the next player goes I'd also like a delay. So right now I have aplayer view a hand view and a card view. The player and hand are both observables.
var body: some View {
HStack {
VStack(alignment: .trailing, spacing: 0){
ForEach(0..<self.player.hands.count, id: \.self) {
index in ZStack {
Spacer()
HandView.init(hand: self.player.hands[index])
}
}
Spacer().frame(height: 45)
Text(self.player.id).bold().font(Font.system(size: 20))
Spacer()
}
.padding(.all)
if !player.isRobot{
VStack{Button(action: {
self.player.requestCard()
}, label: {
Text("Hit Me!")
})
Button(action: {
self.player.handleInput(turn: turnPosibilities.stay)
self.player.objectWillChange.send()
}, label: {
Text("Stay")
})}
.offset(x: 10, y: 0)}
}
}
#ObservedObject var hand:Hand
var bust: some View {
GeometryReader { geometry in
Path { path in
path.move(to: CGPoint.init(x: geometry.frame(in: .local).midX, y: CGFloat(geometry.frame(in: .local).midY)))
path.addLine(to: CGPoint.init(x: geometry.frame(in: .local).minX, y: geometry.frame(in: .local).minY))
path.addLine(to: CGPoint.init(x: geometry.frame(in: .local).minX, y: geometry.frame(in: .local).maxY))
path.addLine(to: CGPoint.init(x: geometry.frame(in: .local).maxX, y: geometry.frame(in: .local).maxY))
path.addLine(to: CGPoint.init(x: geometry.frame(in: .local).maxX, y: geometry.frame(in: .local).minY))
}
.fill(Color.red)
}
}
var body: some View {
ZStack
{ForEach(0..<self.hand.cards.count, id: \.self){
card in
VStack(alignment: .leading, spacing: 2) {PlayingCardView(rank: self.hand.cards[card].rankRaw, suit: self.hand.cards[card].suit, isFaceUp: self.hand.cards[card].isFaceUp ).rotationEffect(Angle.init(degrees: Double(multiply(index: card, offset: 10))))
.offset(x: multiply(index: card, offset: 15), y: multiply(index: card, offset: 40))
}
}
}
}
}
ZStack{
if isFaceUp {
Rectangle().frame(width: 130, height:182).foregroundColor(Color.init(#colorLiteral(red: 0.9999127984, green: 1, blue: 0.9998814464, alpha: 1))).cornerRadius(25).overlay(Image(self.suit.rawValue).resizable().aspectRatio(contentMode: .fit).padding(.all))
Text(String(self.rank)).font(.custom("Poppins-SemiBoldItalic", size: 40)).offset(x: 29, y: -70)
Text(String(self.rank)).font(.custom("Poppins-SemiBoldItalic", size: 40)).offset(x: 29, y: -70).rotationEffect(Angle.init(degrees: 180))
}
else {
Rectangle().frame(width: 130, height:182).foregroundColor(Color.init(UIColor(red: 0.58, green: 0.65, blue: 0.65, alpha: 1.00)
)).cornerRadius(25)
}
}.clipShape(RoundedRectangle(cornerRadius: 25)).overlay(RoundedRectangle(cornerRadius: 25).stroke(Color.black, lineWidth: 2))
}
Here is possible approach
VStack{Button(action: {
DispatchQueue.main.asyncAfter(deadline: .now() + 1) { // 1 sec delay
self.player.requestCard()
}
}, label: {
Text("Hit Me!")
})
This can be achieved in SwiftUI by defining a #State variable / flag and showing new view based on that. In the below code snippet showSecondView determines when to show the next view.
struct ContentView: View {
#State var showSecondView = false
var body: some View {
Group {
Text("Hello, World!")
.font(.largeTitle)
.foregroundColor(.primary)
if showSecondView {
Text("SecondView")
.font(.title)
.foregroundColor(.secondary)
}
}
.onAppear() {
Timer.scheduledTimer(withTimeInterval: 2, repeats: false) { (_) in
withAnimation {
self.showSecondView = true
}
}
}
}

SwiftUI list animations

I am following Apple's Swift UI Animating Views And Transitions and I noticed a bug in the Hike Graph View. When I click on the graph it does not allow me to switch from Elevation to Heart Rate or Pace. It does not let me and just exits the view. I think this has something to do with the List here:
VStack(alignment: .leading) {
Text("Recent Hikes")
.font(.headline)
HikeView(hike: hikeData[0])
}
Hike View Contains:
import SwiftUI
struct HikeView: View {
var hike: Hike
#State private var showDetail = false
var transition: AnyTransition {
let insertion = AnyTransition.move(edge: .trailing)
.combined(with: .opacity)
let removal = AnyTransition.scale
.combined(with: .opacity)
return .asymmetric(insertion: insertion, removal: removal)
}
var body: some View {
VStack {
HStack {
HikeGraph(hike: hike, path: \.elevation)
.frame(width: 50, height: 30)
.animation(nil)
VStack(alignment: .leading) {
Text(hike.name)
.font(.headline)
Text(hike.distanceText)
}
Spacer()
Button(action: {
withAnimation {
self.showDetail.toggle()
}
}) {
Image(systemName: "chevron.right.circle")
.imageScale(.large)
.rotationEffect(.degrees(showDetail ? 90 : 0))
.scaleEffect(showDetail ? 1.5 : 1)
.padding()
}
}
if showDetail {
HikeDetail(hike: hike)
.transition(transition)
}
}
}
}
Hike Detail Contains:
struct HikeDetail: View {
let hike: Hike
#State var dataToShow = \Hike.Observation.elevation
var buttons = [
("Elevation", \Hike.Observation.elevation),
("Heart Rate", \Hike.Observation.heartRate),
("Pace", \Hike.Observation.pace),
]
var body: some View {
return VStack {
HikeGraph(hike: hike, path: dataToShow)
.frame(height: 200)
HStack(spacing: 25) {
ForEach(buttons, id: \.0) { value in
Button(action: {
self.dataToShow = value.1
}) {
Text(value.0)
.font(.system(size: 15))
.foregroundColor(value.1 == self.dataToShow
? Color.gray
: Color.accentColor)
.animation(nil)
}
}
}
}
}
}
Hike Graoh Contains:
import SwiftUI
func rangeOfRanges<C: Collection>(_ ranges: C) -> Range<Double>
where C.Element == Range<Double> {
guard !ranges.isEmpty else { return 0..<0 }
let low = ranges.lazy.map { $0.lowerBound }.min()!
let high = ranges.lazy.map { $0.upperBound }.max()!
return low..<high
}
func magnitude(of range: Range<Double>) -> Double {
return range.upperBound - range.lowerBound
}
extension Animation {
static func ripple(index: Int) -> Animation {
Animation.spring(dampingFraction: 0.5)
.speed(2)
.delay(0.03 * Double(index))
}
}
struct HikeGraph: View {
var hike: Hike
var path: KeyPath<Hike.Observation, Range<Double>>
var color: Color {
switch path {
case \.elevation:
return .gray
case \.heartRate:
return Color(hue: 0, saturation: 0.5, brightness: 0.7)
case \.pace:
return Color(hue: 0.7, saturation: 0.4, brightness: 0.7)
default:
return .black
}
}
var body: some View {
let data = hike.observations
let overallRange = rangeOfRanges(data.lazy.map { $0[keyPath: self.path] })
let maxMagnitude = data.map { magnitude(of: $0[keyPath: path]) }.max()!
let heightRatio = (1 - CGFloat(maxMagnitude / magnitude(of: overallRange))) / 2
return GeometryReader { proxy in
HStack(alignment: .bottom, spacing: proxy.size.width / 120) {
ForEach(data.indices) { index in
GraphCapsule(
index: index,
height: proxy.size.height,
range: data[index][keyPath: self.path],
overallRange: overallRange)
.colorMultiply(self.color)
.transition(.slide)
.animation(.ripple(index: index))
}
.offset(x: 0, y: proxy.size.height * heightRatio)
}
}
}
}
Graph Capsule Contains:
import SwiftUI
struct GraphCapsule: View {
var index: Int
var height: CGFloat
var range: Range<Double>
var overallRange: Range<Double>
var heightRatio: CGFloat {
max(CGFloat(magnitude(of: range) / magnitude(of: overallRange)), 0.15)
}
var offsetRatio: CGFloat {
CGFloat((range.lowerBound - overallRange.lowerBound) / magnitude(of: overallRange))
}
var body: some View {
Capsule()
.fill(Color.white)
.frame(height: height * heightRatio)
.offset(x: 0, y: height * -offsetRatio)
}
}
Is there any way to fix this? Thanks
The problem might be deeper in SwiftUI - if you comment out transition(.slide) in HikeGraph (and restart the XCODE), it will start working