Skip to content

Instantly share code, notes, and snippets.

  • Star 3 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
Star You must be signed in to star a gist
Save balazserd/884c54b2111383b5a891453dbdea3f3d to your computer and use it in GitHub Desktop.
//
// TransactionView.swift
//
// Created by Balazs Erdesz on 2021. 02. 23..
//
import SwiftUI
import Combine
struct TransactionView: View {
@ObservedObject var model: ViewModel
@State private var selected: Bool = false
var body: some View {
VStack(alignment: .leading, spacing: 0) {
HStack {
Text(model.categoryName)
.font(.system(size: 14))
.foregroundColor(.secondary)
Spacer()
// Text("\(model.time)")
Text("2d")
.font(.system(size: 12, weight: .semibold))
.foregroundColor(.secondary)
Image(systemName: "chevron.up")
.rotationEffect(selected ? .degrees(-180) : .zero)
}
Divider()
.frame(height: 6)
.padding(.bottom, 4)
HStack(alignment: .firstTextBaseline) {
Text(model.transactionName)
.font(.system(size: 17, weight: .semibold))
.frame(maxWidth: 200, alignment: .leading)
.lineLimit(2)
Spacer()
// Text("\(model.time)")
if selected {
Text("Feb 10, 2021 13:35")
.font(.system(size: 12))
.foregroundColor(.secondary)
}
}
.frame(height: selected ? nil : 40, alignment: .top)
.anchorPreference(key: AmountLocationPreferenceKey.self, value: .bottomTrailing, transform: {
[AmountLocationPreferenceKey.Data(isSelected: false, anchorPoint: $0)]
})
if selected {
VStack(spacing: 3) {
TransactionBudgetLine(model: TransactionBudgetLine.ViewModel())
TransactionBudgetLine(model: TransactionBudgetLine.ViewModel())
HStack(alignment: .firstTextBaseline, spacing: 0) {
Text("Paid: ")
.font(.system(size: 12))
.anchorPreference(key: AmountLocationPreferenceKey.self, value: .bottomTrailing, transform: {
[AmountLocationPreferenceKey.Data(isSelected: true, anchorPoint: $0)]
})
Spacer()
Text("New balance: ")
.font(.system(size: 12))
Text("$12455.35")
.font(.system(size: 12, weight: .semibold))
}
.padding(.top, 6)
}
.transition(AnyTransition.height.animation(.linear(duration: 0.2)))
.padding(.top, 4)
}
}
.overlayPreferenceValue(AmountLocationPreferenceKey.self) { preferenceValue in
GeometryReader { proxy in
drawAmountLabel(in: proxy, preferenceValue: preferenceValue)
}
}
.padding(8)
.background(
RoundedRectangle(cornerRadius: 8)
.fill(selected ? Colors.Green.typed(.extraLightGreen) : .white)
.overlay(
RoundedRectangle(cornerRadius: 8)
.stroke(Colors.Green.typed(.mediumGreen),
lineWidth: selected ? 1.5 : 0))
.shadow(x: 0, y: 2, blur: 10, spread: -4)
)
.onTapGesture {
withAnimation(.linear(duration: 0.2)) {
selected.toggle()
}
}
}
private func drawAmountLabel(in proxy: GeometryProxy, preferenceValue: AmountLocationPreferenceKey.Value) -> some View {
let preference = preferenceValue
.first { $0.isSelected == self.selected }!
.anchorPoint
let anchoringPoint = proxy[preference]
return ZStack {
Text("$-9.97")
.font(.system(size: selected ? 15 : 17, weight: .bold))
.foregroundColor(model.transactionAmount < 0
? .red
: .green)
.alignmentGuide(.leading, computeValue: { d in d[self.selected ? .leading : .trailing] - anchoringPoint.x })
.alignmentGuide(.top, computeValue: { d in d[.bottom] - anchoringPoint.y })
}
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading)
}
}
extension TransactionView {
struct AmountLocationPreferenceKey : PreferenceKey {
typealias Value = [Data]
static var defaultValue: [Data] = []
static func reduce(value: inout [Data], nextValue: () -> [Data]) {
value.append(contentsOf: nextValue())
}
struct Data {
var isSelected: Bool
var anchorPoint: Anchor<CGPoint>
}
}
}
extension TransactionView {
class ViewModel: ObservableObject {
@Published var categoryName: String = "Food & Drinks"
@Published var time: Date = Date()
@Published var transactionName: String = "Starbucks coffee"
@Published var transactionAmount: Double = -9.97
}
}
struct TransactionView_Previews: PreviewProvider {
static var previews: some View {
TransactionView(model: TransactionView.ViewModel())
.frame(maxHeight: 300)
.padding()
}
}
extension AnyTransition {
static let height = AnyTransition.modifier(active: HeightTransition(scale: 0.0),
identity: HeightTransition(scale: 1.0))
struct HeightTransition: ViewModifier {
var scale: CGFloat
func body(content: Content) -> some View {
content.scaleEffect(CGSize(width: 1.0, height: scale),
anchor: .center)
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment