I'm using a ternary operator in my swiftui view to change the foreground color of an item.
When using this as code everything compiles normal:
Circle()
.frame(width: 10, height: 10)
.foregroundColor(item.amount < 10 ? Color.green : Color.red)
When using this, my project does not build, CPU of my Mac starts spiking, fans kicking in etc. Anyone an idea what's wrong ?
Circle()
.frame(width: 10, height: 10)
.foregroundColor(item.amount < 10 ? Color.green : (item.amount < 100 ? Color.orange : Color.red))
Complete code:
struct ContentView: View {
#ObservedObject var expenses = Expenses()
#State private var showAddExpense = false
var body: some View {
NavigationView {
List {
ForEach (expenses.items) { item in
HStack {
VStack(alignment: .leading) {
Text(item.name)
.font(.headline)
Text(item.type)
}
Spacer()
Text("€\(item.amount)")
Circle()
.frame(width: 10, height: 10)
.foregroundColor(item.amount < 10 ? Color.green : (item.amount < 100 ? Color.orange : Color.red))
}
}
.onDelete(perform: removeItem)
}
.navigationBarTitle("iExpense")
.navigationBarItems(leading: EditButton(), trailing:
Button(action: {
self.showAddExpense = true
}
) {
Image(systemName: "plus")
})
}
.sheet(isPresented: $showAddExpense) {
AddView(expenses: self.expenses)
}
}
func removeItem(at index: IndexSet) {
expenses.items.remove(atOffsets: index)
}
}
Error showing on the sheet modifier, but this one is correct.
Break body construction for smaller components, like below
ForEach (expenses.items) { item in
self.listRow(for: item) // << extract !!
}
.onDelete(perform: removeItem)
,say, to private row generator function
private func listRow(for item: Item) -> some View { // << your item type here
HStack {
VStack(alignment: .leading) {
Text(item.name)
.font(.headline)
Text(item.type)
}
Spacer()
Text("€\(item.amount)")
Circle()
.frame(width: 10, height: 10)
.foregroundColor(item.amount < 10 ? Color.green : (item.amount < 100 ? Color.orange : Color.red))
}
}
Related
The problem is quite simple. I want to build something like Pedantix https://cemantix.certitudes.org/pedantix in SwiftUI.
I've this already :
So, I try to have my RoundedRectangle overlay to totally hide my text. And I want blocks to go at the line if needed, etc. I tried LazyHGrid (actually this), LazyVGrid, custom grid. But no results ...
import SwiftUI
struct Word: Identifiable, Equatable {
var id = UUID()
var text: String
var isFramed: Bool
var isTouched: Bool
}
struct ContentView: View {
#EnvironmentObject var service: Service
let rows = [
GridItem(.adaptive(minimum: 30)),
]
var body: some View {
GeometryReader { gr in
ScrollView {
VStack(alignment: .leading) {
HStack {
Spacer()
Image(systemName: "arrow.counterclockwise.circle")
.resizable()
.scaledToFit()
.frame(width: 24)
.onTapGesture {
service.loadRandomMovies(page: 1, completion: { _ in
service.loadMovie(id: service.randomMovieId ?? 0, completion: { _ in })
service.loadCredits(id: service.randomMovieId ?? 0, completion: { _ in })
})
}
}
HStack {
VStack {
RoundedRectangle(cornerRadius: 8)
.frame(width: 150, height: 250)
}
.padding()
VStack(alignment: .center) {
customTextView(with: service.frame(in: .title))
.padding(.bottom, 8)
customTextView(with: service.frame(in: .genres))
.padding(.bottom, 8)
.frame(width: gr.size.width * 0.8)
Text("\(service.movie?.releaseDate ?? "")")
.font(.title3)
.padding(.bottom, 8)
if service.movie?.tagline != "" {
Text("\"\(service.movie?.tagline ?? "")\"")
.font(.title3)
.padding(.bottom, 8)
.frame(alignment: .center)
}
customTextView(with: service.frame(in: .overview))
.padding(.bottom, 8)
.frame(width: gr.size.width * 0.8)
Text("\(service.credits?.cast.map({ $0.name }).joined(separator: " - ") ?? "")")
.fontWeight(.bold)
}
}
}
.padding()
}
.frame(width: gr.size.width)
}
}
}
extension ContentView {
#ViewBuilder
func customTextView(with words: [Word]) -> some View {
VStack {
LazyHGrid(rows: rows, spacing: 2) {
ForEach(words) { word -> AnyView in
if word.isFramed {
return AnyView(
Text("\(word.text)")
.padding(2)
.overlay(RoundedRectangle(cornerRadius: 4))
.overlay {
if word.isTouched {
Text("\(word.text.count)")
.foregroundColor(Color.cyan)
}
}
)
}
return AnyView(Text(word.text))
}
}
}
}
}
Do you think you could post your code so that we can see what you have done?
I'm trying to implement a list which can be navigated with arrow keys - up/down. I've created layout, but now I don't totally understand how(and where) to make up/down keys intercepted so I could add my custom logic. I already tried onMoveCommand with focusable but that did not work(wasn't firing at all)
Code I have - below
public var body: some View {
VStack(spacing: 0.0) {
VStack {
HStack(alignment: .center, spacing: 0) {
Image(systemName: "command")
.resizable()
.scaledToFit()
.frame(width: 20, height: 20)
.padding(.leading, 20)
.offset(x: 0, y: 1)
TextField("Search Commands", text: $state.commandQuery)
.font(.system(size: 20, weight: .light, design: .default))
.textFieldStyle(.plain)
.onReceive(
state.$commandQuery
.debounce(for: .seconds(0.1), scheduler: DispatchQueue.main)
) { val in
state.fetchMatchingCommands(val: val)
}
.padding(16)
.foregroundColor(Color(.systemGray).opacity(0.85))
.background(EffectView(.sidebar, blendingMode: .behindWindow))
}
}
Divider()
VStack(spacing: 0) {
List(state.filteredCommands.isEmpty && state.commandQuery.isEmpty ?
commandManager.commands : state.filteredCommands, selection: $selectedItem) { command in
VStack(alignment: .leading, spacing: 0) {
Text(command.title).foregroundColor(Color.white)
.padding(EdgeInsets.init(top: 0, leading: 10, bottom: 0, trailing: 0))
.frame(height: 10)
}.frame(maxWidth: .infinity, maxHeight: 15, alignment: .leading)
.listRowBackground(self.selectedItem == command ?
RoundedRectangle(cornerRadius: 5, style: .continuous)
.fill(Color(.systemBlue)) :
RoundedRectangle(cornerRadius: 5, style: .continuous)
.fill(Color.clear) )
.onTapGesture {
self.selectedItem = command
callHandler(command: command)
}.onHover(perform: { _ in self.selectedItem = command })
}.listStyle(SidebarListStyle())
}
}
.background(EffectView(.sidebar, blendingMode: .behindWindow))
.foregroundColor(.gray)
.edgesIgnoringSafeArea(.vertical)
.frame(minWidth: 600,
minHeight: self.state.isShowingCommandsList ? 400 : 28,
maxHeight: self.state.isShowingCommandsList ? .infinity : 28)
}
This is how it looks - and I want to make focus move between found list items
If I understand your question correctly, you want to use the arrow keys to "move" from the search TextField, to the list of items, and then navigate the list with the up/down arrow keys.
Try something simple like this example code, to monitor the up/down arrow keys, and take the appropriate action.
Adjust/tweak the logic to suit your needs.
import Foundation
import SwiftUI
import AppKit
struct ContentView: View {
let fruits = ["apples", "pears", "bananas", "apricot", "oranges"]
#State var selection: Int?
#State var search = ""
var body: some View {
VStack {
VStack {
HStack(alignment: .center, spacing: 0) {
Image(systemName: "command")
.resizable()
.scaledToFit()
.frame(width: 20, height: 20)
.padding(.leading, 20)
.offset(x: 0, y: 1)
TextField("Search", text: $search)
.font(.system(size: 20, weight: .light, design: .default))
.textFieldStyle(.plain)
}
}
Divider()
List(selection: $selection) {
ForEach(fruits.indices, id: \.self) { index in
Text(fruits[index]).tag(index)
}
}
.listStyle(.bordered(alternatesRowBackgrounds: true))
}
.onAppear {
NSEvent.addLocalMonitorForEvents(matching: [.keyDown]) { nsevent in
if selection != nil {
if nsevent.keyCode == 125 { // arrow down
selection = selection! < fruits.count ? selection! + 1 : 0
} else {
if nsevent.keyCode == 126 { // arrow up
selection = selection! > 1 ? selection! - 1 : 0
}
}
} else {
selection = 0
}
return nsevent
}
}
}
}
I am having problems with making a simple systemIcon flash in SwiftUI.
I got the animation working, but it has a silly behaviour if the layout of
a LazyGridView changes or adapts. Below is a video of its erroneous behaviour.
The flashing bell stays in place but when the layout rearranges the bell
starts transitioning in from the bottom of the parent view thats not there anymore.
Has someone got a suggestion how to get around this?
Here is a working example which is similar to my problem
import SwiftUI
struct FlashingBellLazyVGrid: View {
#State var isAnimating = false
#State var showChart = true
var body: some View {
let columns = [GridItem(.adaptive(minimum: 300), spacing: 50, alignment: .center)]
VStack {
Button(action: {
showChart.toggle()
}) {
VStack {
Circle()
.fill(showChart ? Color.green : Color.red)
.shadow(color: Color.gray, radius: 5, x: 2, y: 2)
Text("Charts")
.foregroundColor(Color.primary)
}.frame(width: 150, height: 50)
}
ScrollView {
LazyVGrid (
columns: columns, spacing: 50
) {
ForEach(0 ..< 25) { item in
ZStack {
Rectangle()
.fill(Color.red)
.cornerRadius(15)
VStack {
HStack {
Text(/*#START_MENU_TOKEN#*/"Hello, World!"/*#END_MENU_TOKEN#*/)
Spacer()
Image(systemName: "bell.fill")
.foregroundColor(Color.yellow)
.opacity(self.isAnimating ? 1 : 0)
.animation(Animation.easeInOut(duration: 0.66).repeatForever(autoreverses: false))
.onAppear{ self.isAnimating = true }
}.padding(50)
if showChart {
Rectangle()
.fill(Color.green)
.frame(height: 200)
}
}
}
}
}
}
}
}
}
struct FlashingBellLazyVGrid_Previews: PreviewProvider {
static var previews: some View {
FlashingBellLazyVGrid()
}
}
how it looks like before you click the showChart button at the top
After you toggle the button it looks like the bells are erroneously moving into place from the bottom of the screen. and toggling it back to its original state doesn't resolve this bug subsequently.
[
Looks like the animation is basing itself off of the original size of the view. In order to trick it into recognizing the new view size, I used .id(UUID()) on the outside of the grid. In a real world application, you'd probably want to be careful to store this ID somewhere and only refresh it when needed -- not on every re-render like I'm doing:
struct FlashingBellLazyVGrid: View {
#State var showChart = true
let columns = [GridItem(.adaptive(minimum: 300), spacing: 50, alignment: .center)]
var body: some View {
VStack {
Button(action: {
showChart.toggle()
}) {
VStack {
Circle()
.fill(showChart ? Color.green : Color.red)
.shadow(color: Color.gray, radius: 5, x: 2, y: 2)
Text("Charts")
.foregroundColor(Color.primary)
}.frame(width: 150, height: 50)
}
ScrollView {
LazyVGrid (
columns: columns, spacing: 50
) {
ForEach(0 ..< 25) { item in
ZStack {
Rectangle()
.fill(Color.red)
.cornerRadius(15)
VStack {
SeparateComponent()
if showChart {
Rectangle()
.fill(Color.green)
.frame(height: 200)
}
}
}
}
}
.id(UUID()) //<-- Here
}
}
}
}
struct SeparateComponent : View {
#State var isAnimating : Bool = false
var body: some View {
HStack {
Text("Hello, World!")
Spacer()
Image(systemName: "bell.fill")
.foregroundColor(Color.yellow)
.opacity(self.isAnimating ? 1 : 0)
.animation(Animation.easeInOut(duration: 0.66).repeatForever(autoreverses: false))
.onAppear{
self.isAnimating = true
}
}
.padding(50)
}
}
I also separated out the blinking component into its own view, since there were already problematic things happening with the existing logic with onAppear, which wouldn't affect newly-scrolled-to items correctly. This may need refactoring for your particular case as well, but this should get you started.
I have created an HStack that houses a collection of buttons that change appearance when selected and deselected. Only one button can be selected at a time. When adding a Spacer() between items, the button toggle works, however the appearance no longer changes, even though the index selected is changing as it's supposed to.
struct GenericFilterButton: View {
var category: String
var textColor: Color
var buttonColor: Color
var body: some View {
Text(category)
.font(.custom("Avenir Heavy", size: 14))
.foregroundColor(textColor)
.padding(.vertical, 10)
.padding(.horizontal, 14)
.background(buttonColor)
.cornerRadius(50)
.lineLimit(1)
}
}
struct FilterViewCompletionPercent: View {
var completionPercent = ["Any", "25%", "50%", "75%", "100%"]
#State var percentSelected = 0
var body: some View {
VStack(alignment: .leading, spacing: 0) {
Text("Completion")
.font(.custom("Avenir Heavy", size: 18))
.foregroundColor(Color.black.opacity(0.5))
.padding(.bottom, 20)
HStack(alignment: .center) {
ForEach(0..<completionPercent.count) { i in
Button(action: {
self.percentSelected = i
print(self.percentSelected)
}) {
GenericFilterButton(category: self.completionPercent[i], textColor: self.percentSelected == i ? Color.white : Color.black.opacity(0.5), buttonColor: self.percentSelected == i ? Color.blue : Color.white.opacity(0.0))
}
if i != 4 {
Spacer()
}
}
}.padding(.bottom, 20)
Divider()
.background(Color.black.opacity(0.1))
}.padding(.horizontal, 25)
.padding(.top, 30)
}
}
Why may this be happening? I can't seem to think of a plausible reason why adding a Spacer() between items doesn't work, but when adding a Spacer() without the if statement does.
Dynamic content of ForEach should be single view, so embed your Button-Spacer pair into another HStack.
Tested with Xcode 11.4 / iOS 13.4
HStack(alignment: .center) {
ForEach(0..<completionPercent.count, id: \.self) { i in
HStack { // << this one !!
Button(action: {
self.percentSelected = i
print(self.percentSelected)
}) {
GenericFilterButton(category: self.completionPercent[i], textColor: self.percentSelected == i ? Color.white : Color.black.opacity(0.5), buttonColor: self.percentSelected == i ? Color.blue : Color.white.opacity(0.0))
}
if i != 4 {
Spacer()
}
}
}
}.padding(.bottom, 20)
I'm working on a checklist in SwiftUI and want to define the amount of checkboxes in my data. So for example, in case of fruit, the user should tick 3 checkboxes whereas water requires more checks because you need to drink more glasses of water a day.
I'm using "for each" in a HStack to make an array of checkboxes:
HStack {
ForEach(0 ..< 3) { index in
VStack {
Button(action: {
self.checked[index].toggle()
})
I want to replace the ( 0..<3) with checklist.steps which is where the amount (INT) is defined.
let checklistData = [
Checklist(title: "Fruit", instruction: "1 vakje = 1 stuk of 100g", steps: Int(4)),
Is there a way to define the amount of checkboxes in my data?
Thanks in advance <3
Solution
The following code worked for me:
HStack {
ForEach(0 ..< checklist.steps) { index in
VStack {
Button(action: {
self.checked[index].toggle()
})
i am not sure whether you want this?
HStack {
ForEach(0 ..< checklistData[x].steps) { index in
VStack {
Button(action: {
self.checked[index].toggle()
})
ok, here my better answer, thanks for your code:
struct ChecklistView: View {
var checklist: Checklist
#State var currentProgress: Float = 0.0
#State var checked = \[false, false, false, false\]
var body: some View {
ZStack(alignment: .leading) {
// RoundedRectangle(cornerRadius: 20)
// .foregroundColor(.red).opacity(0.5)
// .frame(width: 200)
VStack {
HStack(alignment: .top) {
checklist.image
.resizable()
.aspectRatio(contentMode: .fit)
.frame(width: 40, height: 40)
.padding(.trailing)
VStack(alignment: .leading) {
Text(checklist.title)
.font(.system(size: 16, weight: .medium))
.padding(.bottom, 4)
Text(checklist.instruction.uppercased()).font(.system(size: 12))
HStack {
ForEach(0 ..< checklist.steps) { index in
VStack {
Button(action: {
self.checked\[index\].toggle()
}) {
ZStack {
RoundedRectangle(cornerRadius: 8)
.foregroundColor(self.checked\[index\] ? Color("LightGreen") : .gray )
.frame(width: 40, height: 40)
Image(systemName: self.checked\[index\] ? "checkmark" : "plus")
.resizable()
.aspectRatio(contentMode: .fit)
.frame(width: 16, height: 16)
.foregroundColor(self.checked\[index\] ? .white : .white)
}
}
}
}
}
}
}.frame(width: 350, alignment: .leading)
}
}
.frame(width: 350)
.padding(.bottom, 16)
.cornerRadius(8)
.padding(.top, 20)
}
}
// MARK: data checklist
struct Checklist: Identifiable, Hashable {
func hash(into hasher: inout Hasher) {
hasher.combine(id)
hasher.combine(title)
}
var id = UUID().uuidString
var title: String
var instruction: String
var image: Image
var steps: Int
}
struct ContentView: View {
let checklistData = \[
Checklist(title: "Fruit", instruction: "1 vakje = 1 stuk of 100g", image: Image("kiwi"), steps: Int(4)),
Checklist(title: "Fruit", instruction: "1 vakje = 1 stuk of 100g", image: Image("kiwi"), steps: Int(2))
\]
var body: some View {
List(checklistData, id: \.self) { checkList in
ChecklistView(checklist: checkList)
}
}
}