Last active
January 20, 2024 23:31
-
-
Save chase-seibert/6f135543290572befb5213a863bc666a to your computer and use it in GitHub Desktop.
100DaysofSwiftUI.swift
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
@main | |
struct Test1App: App { | |
var body: some Scene { | |
WindowGroup { | |
ContentView() | |
} | |
} | |
} | |
// | |
// ContentView.swift | |
// Test2 | |
// | |
// Created by cseibert on 12/18/23. | |
// | |
import SwiftUI | |
struct ContentView: View { | |
@State private var checkAmount = 0.0 | |
@State private var numberOfPeople = 2 | |
@State private var tipPercentage = 20 | |
@FocusState private var amountIsFocused: Bool | |
//let tipPercentages = [10, 15, 20, 25, 0] | |
let tipPercentages = 0..<101 | |
var grandTotal: Double { | |
let tipSelection = Double(tipPercentage) | |
let tipValue = checkAmount / 100 * tipSelection | |
return checkAmount + tipValue | |
} | |
var totalPerPerson: Double { | |
let peopleCount = Double(numberOfPeople) | |
let amountPerPerson = grandTotal / peopleCount | |
return amountPerPerson | |
} | |
var body: some View { | |
NavigationStack { | |
Form { | |
Section { | |
TextField("Amount", value: $checkAmount, format: .currency(code: Locale.current.currency?.identifier ?? "USD")) | |
.focused($amountIsFocused) | |
} | |
Section("How much tip do you want to leave?") { | |
Picker("Tip percentage", selection: $tipPercentage) { | |
ForEach(tipPercentages, id: \.self) { | |
Text($0, format: .percent) | |
} | |
} | |
} | |
Section { | |
Picker("Number of people", selection: $numberOfPeople) { | |
ForEach(2..<100) { | |
Text("\($0) people").tag($0) | |
} | |
} | |
} | |
Section("Amount per person") { | |
Text(totalPerPerson, format: .currency(code: Locale.current.currency?.identifier ?? "USD")) | |
} | |
Section("Total check") { | |
Text(grandTotal, format: .currency(code: Locale.current.currency?.identifier ?? "USD")) | |
.foregroundStyle(grandTotal < 100 ? .red : .black) | |
} | |
} | |
.navigationTitle("WeSplit") | |
.toolbar { | |
if amountIsFocused { | |
Button("Done") { | |
amountIsFocused = false | |
} | |
} | |
} | |
} | |
} | |
} | |
#Preview { | |
ContentView() | |
} | |
// | |
// ContentView.swift | |
// Test3 | |
// | |
// Created by cseibert on 12/21/23. | |
// | |
import SwiftUI | |
struct ContentView: View { | |
@State private var checkAmount = "" | |
@State private var numberOfPeople = 2 | |
@State private var tipPercentage = 20 | |
let tipPercentages = [10, 15, 20, 25, 0] | |
var body: some View { | |
Form { | |
Section { | |
TextField("Amount", value: $checkAmount, format: .currency(code: "USD")) | |
} | |
} | |
} | |
} | |
#Preview { | |
ContentView() | |
} | |
// | |
// ContentView.swift | |
// GuessTheFlag | |
// | |
// Created by cseibert on 12/27/23. | |
// | |
import SwiftUI | |
struct ContentView: View { | |
@State private var countries = ["Estonia", "France", "Germany", "Ireland", "Italy", "Nigeria", "Poland", "Spain", "UK", "Ukraine", "US"].shuffled() | |
@State private var correctAnswer = Int.random(in: 0...2) | |
@State private var showingScore = false | |
@State private var scoreTitle = "" | |
@State private var scoreValue = 0 | |
@State private var whichFlag = 0 | |
@State private var rotationAmounts = [0.0, 0.0, 0.0] | |
@State private var opacityAmounts = [1.0, 1.0, 1.0] | |
struct FlagImage: View { | |
var name: String | |
var body: some View { | |
Image(name) | |
.clipShape(.capsule) | |
.shadow(radius: 5) | |
} | |
} | |
var body: some View { | |
ZStack { | |
RadialGradient(stops: [ | |
.init(color: Color(red: 0.1, green: 0.2, blue: 0.45), location: 0.3), | |
.init(color: Color(red: 0.76, green: 0.15, blue: 0.26), location: 0.3), | |
], center: .top, startRadius: 200, endRadius: 400) | |
.ignoresSafeArea() | |
VStack { | |
Spacer() | |
Text("Guess the Flag") | |
.font(.largeTitle.bold()) | |
.foregroundStyle(.white) | |
VStack(spacing: 15) { | |
VStack { | |
Text("Tap the flag of") | |
.foregroundStyle(.secondary) | |
.font(.subheadline.weight(.heavy)) | |
Text(countries[correctAnswer]) | |
.font(.largeTitle.weight(.semibold)) | |
} | |
ForEach(0..<3) { number in | |
Button { | |
whichFlag = number | |
withAnimation(.spring(duration: 0.5, bounce: 0.5)) { | |
rotationAmounts[number] += 360 | |
} | |
opacityAmounts[(number + 1) % 3] = 0.2 | |
opacityAmounts[(number + 2) % 3] = 0.2 | |
flagTapped(number) | |
} label: { | |
FlagImage(name: countries[number]) | |
} | |
.rotation3DEffect(.degrees(rotationAmounts[number]), axis: (x: 0, y: 1, z: 0)) | |
.opacity(opacityAmounts[number]) | |
} | |
} | |
.frame(maxWidth: .infinity) | |
.padding(.vertical, 20) | |
.background(.regularMaterial) | |
.clipShape(.rect(cornerRadius: 20)) | |
.alert(scoreTitle, isPresented: $showingScore) { | |
Button("Continue", action: askQuestion) | |
} message: { | |
Text("Your score is \(scoreValue)") | |
} | |
Spacer() | |
Spacer() | |
Text("Score: \(scoreValue)") | |
.foregroundStyle(.white) | |
.font(.title.bold()) | |
Spacer() | |
} | |
.padding() | |
} | |
} | |
func flagTapped(_ number: Int) { | |
if number == correctAnswer { | |
scoreTitle = "Correct" | |
scoreValue += 1 | |
} else { | |
let incorrectAnswer = countries[number] | |
scoreTitle = "Wrong, that is the flag for \(incorrectAnswer)" | |
} | |
showingScore = true | |
} | |
func askQuestion() { | |
countries.shuffle() | |
correctAnswer = Int.random(in: 0...2) | |
rotationAmounts = [0.0, 0.0, 0.0] | |
opacityAmounts = [1.0, 1.0, 1.0] | |
} | |
} | |
#Preview { | |
ContentView() | |
} | |
// | |
// ContentView.swift | |
// RockPaperScissors | |
// | |
// Created by cseibert on 12/29/23. | |
// | |
import SwiftUI | |
struct ContentView: View { | |
enum RPSChoice: String, CaseIterable { | |
case rock = "mountain.2" | |
case paper = "newspaper" | |
case scissors = "scissors" | |
} | |
@State var goalIsToWin = true | |
@State var score = 0 | |
@State var numQuestions = 0 | |
@State var computerSelection = RPSChoice.allCases.randomElement()! | |
@State var playerSelection: RPSChoice? = nil | |
@State var playerWon = false | |
@State var showResult = false | |
func reset() { | |
showResult = false | |
playerWon = false | |
playerSelection = nil | |
goalIsToWin = !goalIsToWin | |
computerSelection = RPSChoice.allCases.randomElement()! | |
} | |
func makeSelection(selection: RPSChoice) { | |
playerSelection = selection | |
if (computerSelection == RPSChoice.rock) { | |
if (playerSelection == RPSChoice.paper) { | |
playerWon = true | |
} | |
} | |
if (computerSelection == RPSChoice.paper) { | |
if (playerSelection == RPSChoice.scissors) { | |
playerWon = true | |
} | |
} | |
if (computerSelection == RPSChoice.scissors) { | |
if (playerSelection == RPSChoice.rock) { | |
playerWon = true | |
} | |
} | |
if (playerWon && goalIsToWin) { | |
score += 1 | |
} | |
numQuestions += 1 | |
showResult = true | |
} | |
var body: some View { | |
VStack { | |
Text("Rock, Paper, Scissors") | |
.font(/*@START_MENU_TOKEN@*/.title/*@END_MENU_TOKEN@*/) | |
Spacer() | |
Text("Opponent has played") | |
.font(.title2) | |
Image(systemName: computerSelection.rawValue) | |
.font(.largeTitle) | |
.foregroundStyle(.tint) | |
Spacer() | |
if (goalIsToWin) { | |
Text("Your goal is to WIN") | |
.font(.title2) | |
Image(systemName: "checkmark.circle") | |
.font(.largeTitle) | |
.foregroundStyle(Color.green) | |
} else { | |
Text("Your goal is to LOOSE") | |
Image(systemName: "exclamationmark.triangle") | |
.font(.largeTitle) | |
.foregroundStyle(Color.red) | |
} | |
Spacer() | |
Text("Make your choice") | |
.font(.title2) | |
HStack { | |
ForEach(RPSChoice.allCases, id: \.rawValue) { choice in | |
Button { | |
makeSelection(selection: choice) | |
} label: { | |
Image(systemName: choice.rawValue) | |
.font(.largeTitle) | |
.tint(.white) | |
} | |
.imageScale(.large) | |
.foregroundStyle(.tint) | |
.frame(width: 75, height: 35) | |
.padding() | |
.shadow(radius: 5) | |
.background(.blue) | |
.clipShape(.capsule) | |
Spacer() | |
} | |
} | |
} | |
.padding() | |
.background( | |
Rectangle().fill(Color(white: 0.9, opacity: 0.7).gradient).ignoresSafeArea() | |
) | |
.alert(playerWon ? "You WON" : "You LOST", isPresented: $showResult) { | |
Button("Continue", action: reset) | |
} message: { | |
Text("Your score is \(score)") | |
} | |
} | |
} | |
#Preview { | |
ContentView() | |
} | |
// | |
// ContentView.swift | |
// WordScramble | |
// | |
// Created by cseibert on 12/31/23. | |
// | |
import SwiftUI | |
struct ContentView: View { | |
let MIN_LENGTH = 3 | |
@State private var usedWords = [String]() | |
@State private var rootWord = "" | |
@State private var newWord = "" | |
@State private var score = 0 | |
@State private var errorTitle = "" | |
@State private var errorMessage = "" | |
@State private var showingError = false | |
func startGame() { | |
usedWords = [String]() | |
newWord = "" | |
score = 0 | |
if let startWordsURL = Bundle.main.url(forResource: "start", withExtension: "txt") { | |
if let startWords = try? String(contentsOf: startWordsURL) { | |
let allWords = startWords.components(separatedBy: "\n") | |
rootWord = allWords.randomElement() ?? "silkworm" | |
return | |
} | |
} | |
fatalError("Could not load start.txt from bundle.") | |
} | |
func addNewWord() { | |
let answer = newWord.lowercased().trimmingCharacters(in: .whitespacesAndNewlines) | |
guard answer.count > 0 else { return } | |
guard isOriginal(word: answer) else { | |
wordError(title: "Word used already", message: "Be more original") | |
return | |
} | |
guard isPossible(word: answer) else { | |
wordError(title: "Word not possible", message: "You can't spell that word from '\(rootWord)'!") | |
return | |
} | |
guard isReal(word: answer) else { | |
wordError(title: "Word not recognized", message: "You can't just make them up, you know!") | |
return | |
} | |
guard isLongEnough(word: answer) else { | |
wordError(title: "Word is too short", message: "You can't use words that are less than \(MIN_LENGTH) characters long") | |
return | |
} | |
withAnimation { | |
usedWords.insert(answer, at: 0) | |
} | |
score += answer.count | |
newWord = "" | |
} | |
func isOriginal(word: String) -> Bool { | |
!usedWords.contains(word) | |
} | |
func isPossible(word: String) -> Bool { | |
var tempWord = rootWord | |
for letter in word { | |
if let pos = tempWord.firstIndex(of: letter) { | |
tempWord.remove(at: pos) | |
} else { | |
return false | |
} | |
} | |
return true | |
} | |
func isReal(word: String) -> Bool { | |
let checker = UITextChecker() | |
let range = NSRange(location: 0, length: word.utf16.count) | |
let misspelledRange = checker.rangeOfMisspelledWord(in: word, range: range, startingAt: 0, wrap: false, language: "en") | |
return misspelledRange.location == NSNotFound | |
} | |
func isLongEnough(word: String) -> Bool { | |
return word.count >= MIN_LENGTH | |
} | |
func wordError(title: String, message: String) { | |
errorTitle = title | |
errorMessage = message | |
showingError = true | |
} | |
var body: some View { | |
NavigationStack { | |
VStack { | |
HStack { | |
Text("\"\(rootWord)\"") | |
if (score > 0) { | |
Text("\(score) points") | |
.foregroundColor(.green) | |
} | |
} | |
.font(.title) | |
List { | |
Section("Words") { | |
TextField("Enter your word", text: $newWord) | |
.textInputAutocapitalization(.never) | |
.onSubmit(addNewWord) | |
} | |
Section { | |
ForEach(usedWords, id: \.self) { word in | |
HStack { | |
Image(systemName: "\(word.count).circle") | |
Text(word) | |
} | |
} | |
} | |
} | |
.scrollContentBackground(.hidden) | |
} | |
.padding() | |
.frame(maxWidth: .infinity, maxHeight: .infinity) | |
.background(.teal.opacity(0.33).gradient) | |
.navigationTitle("Word Maker") | |
.toolbar { | |
if (!usedWords.isEmpty) { | |
Button("Reset") { | |
startGame() | |
} | |
} | |
} | |
} | |
.onAppear(perform: startGame) | |
.alert(errorTitle, isPresented: $showingError) { } message: { | |
Text(errorMessage) | |
} | |
} | |
} | |
#Preview { | |
ContentView() | |
} | |
// | |
// ContentView.swift | |
// BetterRest | |
// | |
// Created by cseibert on 12/30/23. | |
// | |
import CoreML | |
import SwiftUI | |
struct ContentView: View { | |
static var defaultWakeTime: Date { | |
var components = DateComponents() | |
components.hour = 7 | |
components.minute = 0 | |
return Calendar.current.date(from: components) ?? .now | |
} | |
@State private var wakeUp = defaultWakeTime | |
@State private var sleepAmount = 8.0 | |
@State private var coffeeAmount = 4 | |
@State private var alertTitle = "" | |
@State private var alertMessage = "" | |
@State private var showingAlert = false | |
var calculateBedtime: String { | |
do { | |
let config = MLModelConfiguration() | |
let model = try SleepCalculator(configuration: config) | |
let components = Calendar.current.dateComponents([.hour, .minute], from: wakeUp) | |
let hour = (components.hour ?? 0) * 60 * 60 | |
let minute = (components.minute ?? 0) * 60 | |
let prediction = try model.prediction(wake: Int64(hour + minute), estimatedSleep: sleepAmount, coffee: Int64(coffeeAmount)) | |
let sleepTime = wakeUp - prediction.actualSleep | |
alertTitle = "Your ideal bedtime is…" | |
return sleepTime.formatted(date: .omitted, time: .shortened) | |
} catch { | |
alertTitle = "Error" | |
return "Sorry, there was a problem calculating your bedtime." | |
} | |
} | |
var body: some View { | |
NavigationStack { | |
VStack { | |
Form { | |
Section("When do you want to wake up?") { | |
DatePicker("Please enter a time", selection: $wakeUp, displayedComponents: .hourAndMinute) | |
.labelsHidden() | |
} | |
Section("Desired amount of sleep") { | |
Stepper("\(sleepAmount.formatted()) hours", value: $sleepAmount, in: 4...12, step: 0.25) | |
} | |
Section { | |
Picker("Daily coffee intake", selection: $coffeeAmount) { | |
ForEach(1...20, id: \.self) { | |
Text("^[\($0) cup](inflect: true)").tag($0) | |
} | |
} | |
} | |
} | |
VStack { | |
Text(calculateBedtime).font(.title) | |
Text("Daily Bed Time").font(.headline) | |
} | |
.frame(alignment: .center) | |
} | |
.navigationTitle("BetterRest") | |
} | |
} | |
} | |
#Preview { | |
ContentView() | |
} | |
// | |
// ContentView.swift | |
// Animations | |
// | |
// Created by cseibert on 1/1/24. | |
// | |
import SwiftUI | |
struct ContentView: View { | |
@State private var animationAmount = 0.0 | |
@State private var enabled = false | |
var body: some View { | |
Button("Tap Me") { | |
withAnimation(.spring(duration: 1, bounce: 0.5)) { | |
animationAmount += 360 | |
} | |
} | |
.padding(50) | |
.background(.red) | |
.foregroundStyle(.white) | |
.clipShape(.circle) | |
.rotation3DEffect(.degrees(animationAmount), axis: (x: 1, y: 0, z: 0)) | |
} | |
} | |
#Preview { | |
ContentView() | |
} | |
// | |
// ContentView.swift | |
// Multiplication | |
// | |
// Created by cseibert on 1/2/24. | |
// | |
import SwiftUI | |
// TODO | |
// Ask one question at a time | |
// Scale up/down based on whether they are getting correct answers | |
// Enter answers w/ giant buttons where it tells you how many digits are required | |
// Animation on correct answers | |
struct ContentView: View { | |
@State private var difficultly = 2 // 2...12 | |
@State private var numQuestions = 0 | |
@State private var firstNumber = 0 | |
@State private var secondNumber = 0 | |
@State private var answer = 0 | |
@State private var answerCharacters = [nil, nil, nil] | |
@State private var answerCorrect = false | |
@State private var numberButtonSelected = 0 | |
@State private var partialAnswer = 0 | |
@State private var gameOver = false | |
@State private var backgroundColor = Color.white.gradient | |
@State private var animationAmount = 0.0 | |
@State private var numPad = [ | |
[1, 2, 3], | |
[4, 5, 6], | |
[7, 8, 9], | |
[0, ], | |
] | |
func resetGame() { | |
numQuestions = 0 | |
resetQuestion() | |
} | |
func resetQuestion() { | |
gameOver = false | |
if answerCorrect { | |
difficultly = min(difficultly + 1, 12) | |
} | |
firstNumber = Int.random(in: 2...difficultly) | |
secondNumber = Int.random(in: 2...difficultly) | |
answer = firstNumber * secondNumber | |
answerCharacters = [nil, nil, nil] | |
answerCorrect = false | |
numberButtonSelected = 0 | |
partialAnswer = 0 | |
backgroundColor = Color.white.gradient | |
} | |
func enterNumber(number: Int) { | |
partialAnswer = partialAnswer * 10 + number | |
if partialAnswer == answer { | |
answerCorrect = true | |
gameOver = true | |
} else if String(partialAnswer).count >= String(answer).count { | |
answerCorrect = false | |
gameOver = true | |
} | |
} | |
private var answerLength: Int { | |
return String(answer).count | |
} | |
private var partialAnswerWithPlaceholder: String { | |
if partialAnswer == 0 { | |
return "_" | |
} | |
return String(partialAnswer) | |
// TODO: return "2 _ _" for a three-digit answer with one digit entered, etc | |
} | |
var body: some View { | |
NavigationStack { | |
VStack { | |
Spacer() | |
Section { | |
Text("What is the answer?") | |
.font(.headline) | |
.foregroundStyle(Color.gray) | |
Text("\(firstNumber) x \(secondNumber)") | |
.font(.largeTitle) | |
.fontWeight(/*@START_MENU_TOKEN@*/.bold/*@END_MENU_TOKEN@*/) | |
.foregroundStyle(Color.black) | |
} | |
Spacer() | |
Section { | |
Text("It has ^[\(answerLength) number](inflect: true) in it") | |
.font(.headline) | |
.foregroundStyle(Color.gray) | |
HStack { | |
Text(partialAnswerWithPlaceholder) | |
} | |
.font(.largeTitle) | |
} | |
Spacer() | |
VStack(spacing: 20) { | |
ForEach(numPad, id: \.self) { row in | |
HStack(spacing: 40) { | |
ForEach(row, id: \.self) { number in | |
Button("\(number)") { | |
enterNumber(number: number) | |
if gameOver { | |
withAnimation(.spring(duration: 1.5, bounce: 0.25)) { | |
if answerCorrect { | |
backgroundColor = Color.green.gradient | |
} else { | |
backgroundColor = Color.red.gradient | |
} | |
animationAmount += 360 | |
} completion: { | |
resetGame() | |
} | |
} | |
} | |
.font(/*@START_MENU_TOKEN@*/.title/*@END_MENU_TOKEN@*/) | |
.padding() | |
.frame(width:80, height: 60) | |
.background(.white) | |
.clipShape(.capsule) | |
.shadow(radius: 5) | |
} | |
} | |
} | |
} | |
.rotation3DEffect(.degrees(animationAmount), axis: (x: 0, y: 1, z: 0)) | |
} | |
.navigationTitle("Multiplication") | |
.padding() | |
.frame(maxWidth: .infinity, maxHeight: .infinity) | |
.background(backgroundColor) | |
} | |
.onAppear(perform: resetGame) | |
} | |
} | |
#Preview { | |
ContentView() | |
} | |
// | |
// ContentView.swift | |
// iExpense | |
// | |
// Created by cseibert on 1/5/24. | |
// | |
import SwiftUI | |
import Observation | |
struct ContentView: View { | |
@State private var showingSheet = false | |
var body: some View { | |
Button("Show Sheet") { | |
showingSheet.toggle() | |
} | |
.sheet(isPresented: $showingSheet) { | |
SecondView(name: "@twostraws") | |
} | |
} | |
} | |
struct SecondView: View { | |
let name: String | |
@Environment(\.dismiss) var dismiss | |
var body: some View { | |
Button("Dismiss") { | |
dismiss() | |
} | |
} | |
} | |
#Preview { | |
ContentView() | |
} | |
// | |
// ContentView.swift | |
// iExpense | |
// | |
// Created by cseibert on 1/5/24. | |
// | |
import SwiftUI | |
struct ContentView: View { | |
@State private var numbers = [Int]() | |
@State private var currentNumber = 1 | |
func removeRows(at offsets: IndexSet) { | |
numbers.remove(atOffsets: offsets) | |
} | |
var body: some View { | |
NavigationStack { | |
VStack { | |
List { | |
ForEach(numbers, id: \.self) { | |
Text("Row \($0)") | |
} | |
.onDelete(perform: removeRows) | |
} | |
Button("Add Number") { | |
numbers.append(currentNumber) | |
currentNumber += 1 | |
} | |
} | |
.toolbar { | |
EditButton() | |
} | |
} | |
} | |
} | |
#Preview { | |
ContentView() | |
} | |
// | |
// ContentView.swift | |
// iExpense | |
// | |
// Created by cseibert on 1/5/24. | |
// | |
import SwiftUI | |
struct ContentView: View { | |
// for full control use this | |
@State private var tapCount2 = UserDefaults.standard.integer(forKey: "Tap2") | |
// for simple situations use this | |
@AppStorage("tapCount") private var tapCount = 0 | |
// or persist custom types like this (Codable) | |
struct User: Codable { | |
let firstName: String | |
let lastName: String | |
} | |
@State private var user = User(firstName: "Taylor", lastName: "Swift") | |
var body: some View { | |
Button("Tap count: \(tapCount)") { | |
tapCount += 1 | |
// don't need this with the AppStorage verison! | |
UserDefaults.standard.set(tapCount, forKey: "Tap") | |
// persist the Codable version | |
Button("Save User") { | |
let encoder = JSONEncoder() | |
if let data = try? encoder.encode(user) { | |
UserDefaults.standard.set(data, forKey: "UserData") | |
} | |
} | |
} | |
} | |
} | |
#Preview { | |
ContentView() | |
} | |
// | |
// ContentView.swift | |
// iExpense | |
// | |
// Created by cseibert on 1/5/24. | |
// | |
import SwiftUI | |
struct ExpenseItem: Identifiable, Codable { | |
var id = UUID() | |
let name: String | |
let type: String | |
let amount: Double | |
} | |
@Observable | |
class Expenses { | |
var items = [ExpenseItem]() { | |
didSet { | |
if let encoded = try? JSONEncoder().encode(items) { | |
UserDefaults.standard.set(encoded, forKey: "Items") | |
} | |
} | |
} | |
init() { | |
if let savedItems = UserDefaults.standard.data(forKey: "Items") { | |
if let decodedItems = try? JSONDecoder().decode([ExpenseItem].self, from: savedItems) { | |
items = decodedItems | |
return | |
} | |
} | |
items = [] | |
} | |
} | |
struct ContentView: View { | |
@State private var expenses = Expenses() | |
@State private var showingAddExpense = false | |
func removeItems(at offsets: IndexSet) { | |
expenses.items.remove(atOffsets: offsets) | |
} | |
func expenseColor(amount: Double) -> Color { | |
if amount >= 100.0 { | |
return Color.red | |
} | |
if amount >= 10.0 { | |
return Color.green | |
} | |
return Color.black | |
} | |
var body: some View { | |
NavigationStack { | |
List { | |
ForEach(expenses.items) { item in | |
HStack { | |
VStack(alignment: .leading) { | |
Text(item.name) | |
.font(.headline) | |
Text(item.type) | |
} | |
Spacer() | |
Text(item.amount, format: .currency(code: Locale.current.currency?.identifier ?? "USD")) | |
.foregroundStyle(expenseColor(amount: item.amount)) | |
} | |
} | |
.onDelete(perform: removeItems) | |
} | |
.navigationTitle("iExpense") | |
.toolbar { | |
Button("Add Expense", systemImage: "plus") { | |
showingAddExpense = true | |
} | |
} | |
.sheet(isPresented: $showingAddExpense) { | |
AddView(expenses: expenses) | |
} | |
} | |
} | |
} | |
#Preview { | |
ContentView() | |
} | |
// | |
// AddView.swift | |
// iExpense | |
// | |
// Created by cseibert on 1/15/24. | |
// | |
import SwiftUI | |
struct AddView: View { | |
@State private var name = "" | |
@State private var type = "Personal" | |
@State private var amount = 0.0 | |
let types = ["Business", "Personal"] | |
var expenses: Expenses | |
@Environment(\.dismiss) var dismiss | |
var body: some View { | |
NavigationStack { | |
Form { | |
TextField("Name", text: $name) | |
Picker("Type", selection: $type) { | |
ForEach(types, id: \.self) { | |
Text($0) | |
} | |
} | |
TextField("Amount", value: $amount, format: .currency(code: Locale.current.currency?.identifier ?? "USD")) | |
.keyboardType(.decimalPad) | |
} | |
.navigationTitle("Add new expense") | |
.toolbar { | |
Button("Save") { | |
let item = ExpenseItem(name: name, type: type, amount: amount) | |
expenses.items.append(item) | |
dismiss() | |
} | |
} | |
} | |
} | |
} | |
#Preview { | |
AddView(expenses: Expenses()) | |
} | |
// | |
// ContentView.swift | |
// Moonshot | |
// | |
// Created by cseibert on 1/20/24. | |
// | |
import SwiftUI | |
struct ContentView: View { | |
var body: some View { | |
Image(.chaseSelfPortrait0605) | |
.resizable() | |
.scaledToFit() | |
.containerRelativeFrame(.horizontal) { size, axis in | |
size * 0.8 | |
} | |
} | |
} | |
#Preview { | |
ContentView() | |
} | |
// | |
// ContentView.swift | |
// Moonshot | |
// | |
// Created by cseibert on 1/20/24. | |
// | |
import SwiftUI | |
struct CustomText: View { | |
let text: String | |
var body: some View { | |
Text(text) | |
} | |
init(_ text: String) { | |
print("Creating a new CustomText") | |
self.text = text | |
} | |
} | |
struct ContentView: View { | |
var body: some View { | |
ScrollView { | |
LazyVStack(spacing: 10) { | |
ForEach(0..<100) { | |
CustomText("Item \($0)") | |
.font(.title) | |
} | |
} | |
.frame(maxWidth: .infinity) | |
} | |
} | |
} | |
#Preview { | |
ContentView() | |
} | |
// | |
// ContentView.swift | |
// Moonshot | |
// | |
// Created by cseibert on 1/20/24. | |
// | |
import SwiftUI | |
struct ContentView: View { | |
var body: some View { | |
NavigationStack { | |
List(0..<100) { row in | |
NavigationLink("Row \(row)") { | |
Text("Detail \(row)") | |
} | |
} | |
.navigationTitle("SwiftUI") | |
} | |
} | |
} | |
#Preview { | |
ContentView() | |
} | |
// | |
// ContentView.swift | |
// Moonshot | |
// | |
// Created by cseibert on 1/20/24. | |
// | |
import SwiftUI | |
struct User: Codable { | |
let name: String | |
let address: Address | |
} | |
struct Address: Codable { | |
let street: String | |
let city: String | |
} | |
struct ContentView: View { | |
var body: some View { | |
Button("Decode JSON") { | |
let input = """ | |
{ | |
"name": "Taylor Swift", | |
"address": { | |
"street": "555, Taylor Swift Avenue", | |
"city": "Nashville" | |
} | |
} | |
""" | |
let data = Data(input.utf8) | |
let decoder = JSONDecoder() | |
if let user = try? decoder.decode(User.self, from: data) { | |
print(user.address.street) | |
} | |
} | |
} | |
} | |
#Preview { | |
ContentView() | |
} | |
// | |
// ContentView.swift | |
// Moonshot | |
// | |
// Created by cseibert on 1/20/24. | |
// | |
import SwiftUI | |
struct ContentView: View { | |
let layout = [ | |
GridItem(.adaptive(minimum: 80, maximum: 120)), | |
] | |
var body: some View { | |
ScrollView { | |
LazyVGrid(columns: layout) { | |
ForEach(0..<1000) { | |
Text("Item \($0)") | |
} | |
} | |
} | |
} | |
} | |
#Preview { | |
ContentView() | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment