Custom Rating view with nested H and VStacks leads to weird frame - swiftui

I'm trying to clone this view:
Unfortunately I have a problem with the Geometry Reader (I guess). It's making a bigger frame then the actual rectangle (see blue area):
Same applies to the stars as well:
Can you point me to my issue? Is something nested wrong? I also tried to include Spacer()but this didn't resolve it.
My Code looks like this:
import SwiftUI
struct Ratings: Identifiable {
var id = UUID().uuidString
var ratingCount: CGFloat
var starCount: Int
}
var userRatings: [Ratings] = [
Ratings(ratingCount: 130, starCount: 5),
Ratings(ratingCount: 90, starCount: 4),
Ratings(ratingCount: 25, starCount: 3),
Ratings(ratingCount: 20, starCount: 2),
Ratings(ratingCount: 3, starCount: 1)
]
struct RatingGraphView: View {
var ratings: [Ratings] = [
Ratings(ratingCount: 130, starCount: 5),
Ratings(ratingCount: 90, starCount: 4),
Ratings(ratingCount: 25, starCount: 3),
Ratings(ratingCount: 20, starCount: 2),
Ratings(ratingCount: 3, starCount: 1)
]
var body: some View {
VStack(spacing: 10){
ForEach(ratings){rating in
RatingView(rating: rating)
}
}
.frame(height: 200)
}
#ViewBuilder
func RatingView(rating: Ratings)->some View{
HStack{
HStack{
ForEach(1..<rating.starCount+1){ star in
Image(systemName: "star")
.resizable()
.aspectRatio(contentMode:.fit)
.frame(height:15)
.scaledToFit()
.padding(.bottom)
}
}
.frame(maxWidth:.infinity, alignment: .trailing)
HStack{
VStack{
GeometryReader{proxy in
let size = proxy.size
RoundedRectangle(cornerRadius: 6)
.fill(Color.blue)
.frame(width: (rating.ratingCount / getMax()) * (size.width), height: 15)
}
}
}
}
.padding(.trailing)
}
func getMax()->CGFloat{
let max = ratings.max { first, second in
return second.ratingCount > first.ratingCount
}
return max?.ratingCount ?? 0
}
}
struct RatingGraphView_Previews: PreviewProvider {
static var previews: some View {
RatingGraphView()
}
}
Many Thanks!

test this
struct RatingGraphView: View {
var ratings: [Ratings] = [
Ratings(ratingCount: 130, starCount: 5),
Ratings(ratingCount: 90, starCount: 4),
Ratings(ratingCount: 25, starCount: 3),
Ratings(ratingCount: 20, starCount: 2),
Ratings(ratingCount: 3, starCount: 1)
]
var body: some View {
VStack(spacing: 0){
ForEach(ratings){rating in
RatingView(rating: rating)
}
}
.frame(height: 100)
}
#ViewBuilder
func RatingView(rating: Ratings)->some View{
GeometryReader{proxy in
HStack {
HStack{
Spacer()
ForEach(1..<rating.starCount+1){ star in
Image(systemName: "star")
.resizable()
.aspectRatio(contentMode:.fit)
.frame(height:15)
.scaledToFit()
//.padding(.bottom)
}
}
.frame(width:proxy.size.width/2.5)
let size = proxy.size
RoundedRectangle(cornerRadius: 6)
.fill(Color.blue)
.frame(width: (rating.ratingCount / getMax()) * (proxy.size.width * 1.5/3), height: 15)
}
}
}
func getMax()->CGFloat{
let max = ratings.max { first, second in
return second.ratingCount > first.ratingCount
}
return max?.ratingCount ?? 0
}
}

Related

how should i loop for a top 3 ranking that should be in different places in design in a SwiftUI

I want to show in the user interface of the future data in accordance with the design. But in the design, the 1st person should appear in the middle, and the 2nd place person should appear on the left. The third person should be on the far right. how can i show this in list loop. I am sharing my codes with you as an example. So in summary, the first 3 successful users will be forwarded to me in order of success. How can I get this display?
struct TopSortingUIView: View {
var topSortingList : [TopSorting] = TopSortingList.three
var body: some View {
HStack(spacing:24){
VStack{
Image("mangaa")
.resizable()
.scaledToFill()
.frame(width: 77, height: 77)
.clipShape(Circle())
.shadow(radius: 8)
.shadow(color: Color.init(red: 0.973, green: 0.976, blue: 0.98), radius: 4.15)
.padding(.top, 75)
Text("User1")
.font(.system(size: 12))
.foregroundColor(.secondary)
Text("2000 P")
.bold()
.font(.system(size: 12))
.foregroundColor(.black)
}
VStack{
Image("King")
Image("mangaa")
.resizable()
.scaledToFill()
.frame(width: 77, height: 77)
.clipShape(Circle())
.shadow(radius: 8)
.shadow(color: Color.init(red: 0.973, green: 0.976, blue: 0.98), radius: 4.15)
Text("User2")
.font(.system(size: 12))
.foregroundColor(.secondary)
Text("2824 P")
.bold()
.font(.system(size: 12))
.foregroundColor(.black)
}
VStack{
Image("mangaa")
.resizable()
.scaledToFill()
.frame(width: 77, height: 77)
.clipShape(Circle())
.shadow(radius: 8)
.shadow(color: Color.init(red: 0.973, green: 0.976, blue: 0.98), radius: 4.15)
.padding(.top, 75)
Text("User3")
.font(.system(size: 12))
.foregroundColor(.secondary)
Text("600P")
.bold()
.font(.system(size: 12))
.foregroundColor(.black)
}
}
}
}
struct TopSorting {
let rank : Int
let photoUrl: String
let userName: String
let name: String
let data : String
}
struct TopSortingList {
static let three = [
TopSorting(rank: 1, photoUrl: "mangaa", userName: "user1", name: "us1", data: "2824Ap"),
TopSorting(rank: 2, photoUrl: "mangaa", userName: "user2", name: "us2", data: "2000Ap"),
TopSorting(rank: 3, photoUrl: "mangaa", userName: "user3", name: "us3", data: "600Ap")
]
}
I'm trying to achieve the look you see in the photo. The data and image of the 1st user should be higher than the others
You will have to sort the items in the array with a custom function.
There are many ways of doing this. Here is something that can be reused.
//Protocol that requires that the Model has the rank variable (needed for array extension)
protocol RankingProtocol{
var rank : Int {get}
}
//Declare conformance to the protocol
struct TopSorting: RankingProtocol {
let rank : Int
let photoUrl: String
let userName: String
let name: String
let data : String
}
//Create an extension for any array that has objects that conform to the RankingProtocol
extension Array where Element : RankingProtocol{
func zigZagRankSort() -> Self{
var new: Self = []
//Reposition the objects based on the original index (add to the front or back)
for (idx, item) in self.sorted(by: { lhs, rhs in
lhs.rank < rhs.rank
}).enumerated(){
if idx % 2 != 0{
new.insert(item, at: 0)
}else{
new.append(item)
}
}
return new
}
}
Then with a ForEach and some minor adjustments you can display the objects.
struct TopSortingUIView: View {
var topSortingList : [TopSorting] = TopSortingList.three
var body: some View {
HStack(spacing:24){
ForEach(topSortingList.zigZagRankSort(), id:\.rank) { list in
VStack{
if list.rank == 1{
Image("King")
}
Image(list.photoUrl)
.resizable()
.scaledToFill()
.frame(width: 77, height: 77)
.clipShape(Circle())
.shadow(radius: 8)
.shadow(color: Color.init(red: 0.973, green: 0.976, blue: 0.98), radius: 4.15)
.padding(.top, 75)
Text(list.userName)
.font(.system(size: 12))
.foregroundColor(.secondary)
Text(list.data)
.bold()
.font(.system(size: 12))
.foregroundColor(.black)
}.padding(.bottom, list.rank == 1 ? 75 : nil)
}
}
}
}
I would suggest something like :
// Podium position view
struct TopSortingPositionView: View {
var topSorting: TopSorting
var body: some View {
VStack{
// only for first
if topSorting.rank == 1 {
Image("King")
}
Image(topSorting.photoUrl)
.resizable()
.scaledToFill()
.frame(width: 77, height: 77)
.clipShape(Circle())
.shadow(radius: 8)
.shadow(color: Color.init(red: 0.973, green: 0.976, blue: 0.98), radius: 4.15)
.modifier(ImageOffset(rank: topSorting.rank))
Text(topSorting.userName)
.font(.system(size: 12))
.foregroundColor(.secondary)
Text(topSorting.data)
.bold()
.font(.system(size: 12))
.foregroundColor(.black)
}
}
// the view modifier enable to apply the offset depending on the rank
// you can put here other modifier for the image
struct ImageOffset: ViewModifier {
var rank: Int
func body(content: Content) -> some View {
if rank == 1 {
content
} else {
// only for second and third
content
.padding(.top, 75)
}
}
}
}
struct TopSortingUIView: View {
var topSortingList : [TopSorting] = TopSortingList.three
var body: some View {
HStack(spacing:24){
// no need for a loop for 3 element
TopSortingPositionView(topSorting: topSortingList[1])
TopSortingPositionView(topSorting: topSortingList[0])
TopSortingPositionView(topSorting: topSortingList[2])
}
}
}
}

SwiftUI When I use stroke in clipShape I got error

I edited my question. I shared full code.
I want to bend the end points of the semicircle as in the 1st image. How can I do that ? I tried this code but got an unrelated error.
How can I soften the corners of a half circle?
I created a customShape but I couldn't get the look I wanted.
.clipShape(
circle()
.trim(from: 0.5, to: 1)
.stroke(Color.red, style: StrokeStyle(lineWidth: 30, lineCap: .round, lineJoin: .round))
Error:
ChartData Container:
class ChartDataContainer : ObservableObject {
#Published var chartData =
[ChartData(color: Color(#colorLiteral(red: 1, green: 0.4932718873, blue: 0.4739984274, alpha: 1)), percent: 8, value: 0),
ChartData(color: Color(#colorLiteral(red: 1, green: 0.8323456645, blue: 0.4732058644, alpha: 1)), percent: 15, value: 0),
ChartData(color: Color(#colorLiteral(red: 0.4508578777, green: 0.9882974029, blue: 0.8376303315, alpha: 1)), percent: 32, value: 0),
ChartData(color: Color(#colorLiteral(red: 0.476841867, green: 0.5048075914, blue: 1, alpha: 1)), percent: 45, value: 0)]
func calc(){
var value : CGFloat = 0
for i in 0..<chartData.count {
value += chartData[i].percent
chartData[i].value = value
}
}
}
Custom Shape:
struct CustomShape: View {
var body: some View {
HalfCircle()
.stroke(.red, style: StrokeStyle(lineWidth: 30, lineCap: .round, lineJoin: .round))
.frame(width: 300, height: 300)
}
}
struct CustomShape_Previews: PreviewProvider {
static var previews: some View {
CustomShape()
}
}
struct HalfCircle: Shape {
func path(in rect: CGRect) -> Path {
var path = Path()
path.addArc(center: CGPoint(x: rect.midX, y: rect.midY), radius: rect.height/2, startAngle: Angle(degrees: 0), endAngle: Angle(degrees: 180), clockwise: true)
return path
}
}
Code:
struct HalfDonutChart : View {
#ObservedObject var charDataObj = ChartDataContainer()
#State var indexOfTappedSlice = -1
var body: some View {
ZStack {
ZStack {
ForEach(0..<charDataObj.chartData.count) { index in
Circle()
.trim(from: 0.5, to: 1)
.trim(from: index == 0 ? 0.0 : charDataObj.chartData[index-1].value/100, to: charDataObj.chartData[index].value/100)
.stroke(charDataObj.chartData[index].color,lineWidth: 30)
.onTapGesture {
indexOfTappedSlice = indexOfTappedSlice == index ? -1 : index
}
.scaleEffect(index == indexOfTappedSlice ? 1.1 : 1.0)
.animation(.spring())
}
if indexOfTappedSlice != -1 {
Text(String(format: "%.2f", Double(charDataObj.chartData[indexOfTappedSlice].percent))+"%")
.font(.title)
}
}
.mask(
CustomShape()
)
.frame(width: 200, height: 250)
.padding()
.onAppear() {
self.charDataObj.calc()
}
}
}
}

How to create cell rows of text that align like cells in SwiftUI

I'd like to create something like this (without the "pipes" between each "cell" in SwiftUI. Assume each "cell" is a fixed size.
I have the following:
import Foundation
struct Activity {
let id: Int
let date: String
let imageURL: String
let distance: Double
let elevation: Int
}
extension Activity {
static func all() -> [Activity] {
return [
Activity(id: 1, date: "10/10/2021", imageURL: "map1", distance: 120, elevation: 452),
Activity(id: 2, date: "10/10/2021", imageURL: "map2", distance: 90, elevation: 34),
Activity(id: 3, date: "10/10/2021", imageURL: "map3", distance: 460, elevation: 345),
Activity(id: 4, date: "10/10/2021", imageURL: "map4", distance: 70, elevation: 21),
Activity(id: 5, date: "10/10/2021", imageURL: "map5", distance: 40, elevation: 345)
]
}
}
struct ContentView: View {
let activities = Activity.all()
var body: some View {
List(self.activities, id: \.id) { activity in
VStack(alignment: .center) {
Image(activity.imageURL)
.resizable()
.frame(width: 300, height: 200)
.cornerRadius(16)
HStack(alignment: .lastTextBaseline) {
Text("Date").fixedSize().font(.system(size: 10))
Spacer()
Text("Distance").fixedSize().font(.system(size: 10))
Spacer()
Text("Elevation").fixedSize().font(.system(size: 10))
}
HStack(alignment: .lastTextBaseline) {
Text(activity.name).fixedSize()
Spacer()
Text(String(activity.distance)).fixedSize()
Spacer()
Text(String(activity.elevation)).fixedSize()
}
}
}
}
}
But clearly this isn't the right approach. Any ideas on how I can create this ?
As far as I understood it can be done as
var body: some View {
List(self.activities, id: \.id) { activity in
VStack(alignment: .center) {
Image(activity.imageURL)
.resizable()
.frame(width: 300, height: 200)
.cornerRadius(16)
HStack(alignment: .lastTextBaseline) {
VStack(alignment: .leading) {
Text("Date").fixedSize().font(.system(size: 10))
Text(activity.date).fixedSize()
}
Spacer()
VStack(alignment: .leading) {
Text("Distance").fixedSize().font(.system(size: 10))
Text(String(activity.distance)).fixedSize()
}
Spacer()
VStack(alignment: .leading) {
Text("Elevation").fixedSize().font(.system(size: 10))
Text(String(activity.elevation)).fixedSize()
}
}
}
}
}

(3) Errors when calling data from API using Contentful & SwiftUI

Forgive me as I am extremely novice with SwiftUI... I am attempting to pull data from the CMS and put it in my app however it is throwing three errors on each attempt for the data to be retrieved and placed...
The errors are highlighting in the sections that read "api.beers.title", "api.beers.type" and "api.beers.description".
Errors
Value of type 'API' has no dynamic member 'beers' using key path from root type 'API'
Referencing subscript 'subscript(dynamicMember:)' requires wrapper 'ObservedObject.Wrapper'
Initializer 'init(_:)' requires that 'Binding' conform to 'StringProtocol'
API Call Code
func getArray(id: String, completion: #escaping([Entry]) -> ()) {
let query = Query.where(contentTypeId: id)
client.fetchArray(of: Entry.self, matching: query) { result in
switch result {
case .success(let array):
DispatchQueue.main.async {
completion(array.items)
}
case .failure(let error):
print(error)
}
}
}
class API: ObservableObject {
#Published var draft: [Draft] = draftData
init() {
getArray(id: "beers") { (items) in
items.forEach { (item) in
self.draft.append(Draft(
title: item.fields["title"] as! String,
type: item.fields["type"] as! String,
description: item.fields["type"] as! String
))
}
}
}
}
struct LandingPageView: View {
var body: some View {
VStack {
VStack {
Text("Problem Solved")
Text("Brewing Company")
}
.font(.system(size: 24, weight: .bold))
.multilineTextAlignment(.center)
.foregroundColor(Color("TextColor"))
VStack {
Text("NEWS & EVENTS")
.font(.title)
.fontWeight(.bold)
.padding(.top, 40)
.foregroundColor(Color("TextColor"))
NewsTile()
Text("On Draft" .uppercased())
.font(.title)
.fontWeight(.bold)
.padding(.top)
.foregroundColor(Color("TextColor"))
ScrollView(.horizontal, showsIndicators: false) {
HStack(spacing: 20) {
ForEach(draftData) { item in
GeometryReader { geometry in
DraftList(beer: item)
.rotation3DEffect(Angle(degrees: Double(geometry.frame(in: .global).minX - 30) / -20), axis: (x: 0, y: 10.0, z: 0))
}
.frame(width: 275, height: 200)
}
}
.padding(.leading, 30)
.padding(.trailing, 30)
}
}
.frame(width: 400, height: 850)
.background(Color("PageBackground"))
.edgesIgnoringSafeArea(.all)
}
}
}
struct DraftList: View {
var width: CGFloat = 275
var height: CGFloat = 200
#ObservedObject var api = API()
var beer: Draft
var body: some View {
VStack {
Spacer()
Text(api.beers.title)
.font(.system(size: 24, weight: .bold))
.padding(.horizontal, 20)
.frame(width: 275, alignment: .leading)
.foregroundColor(Color("TextColor"))
Text(api.beers.type .uppercased())
.font(.system(size: 14, weight: .bold))
.frame(maxWidth: .infinity, alignment: .leading)
.padding(.horizontal, 20)
Text(api.beers.description)
.font(.system(size: 12))
.padding(.horizontal, 20)
.padding(.top, 10)
Spacer()
HStack {
// Add OnTapGesture to bring to full view + cart options.
Text("Click To Add To Cart")
.font(.footnote)
Image(systemName: "cart")
}
.padding(.bottom)
}
.padding(.horizontal, 20)
.frame(width: width, height: height)
.background(Color("TileOrangeColor"))
.cornerRadius(15)
.shadow(color: Color.black.opacity(0.2), radius: 5, x: 0, y: 5)
}
}
Your draft is array, you can access by index like Text(api.draft[0].title)
#Published var draft: [Draft] = draftData instead #Published var draft: [Draft] = []
Updated:
class API: ObservableObject {
#Published var draft: [Draft] = []
init() {
getArray(id: "beers") { (items) in
items.forEach { (item) in
self.draft.append(Draft(
title: item.fields["title"] as! String,
type: item.fields["type"] as! String,
description: item.fields["type"] as! String
))
}
}
}
}
struct DraftList: View {
var width: CGFloat = 275
var height: CGFloat = 200
#ObservedObject var api = API()
var body: some View {
VStack {
ForEach(api.draft) {item in
Spacer()
Text(item.title)
.font(.system(size: 24, weight: .bold))
.padding(.horizontal, 20)
.frame(width: 275, alignment: .leading)
.foregroundColor(Color("TextColor"))
...
}
}
.padding(.horizontal, 20)
.frame(width: width, height: height)
.background(Color("TileOrangeColor"))
.cornerRadius(15)
.shadow(color: Color.black.opacity(0.2), radius: 5, x: 0, y: 5)
}
}

NavigationView + TextField Background

I'm currently learning SwiftUI and building a todo list app. On the ContentView screen I've got a NavigationView and a button that pops up an "add new task" textfield into the list. I suspect this is not the correct way to implement this but when the textfield shows up the background color doesn't persist. For the life of me I can't figure out how to set the background color. If I move the textfield outside the NavigationView I can set the background but when the NavigationView shifts to make space for the textfield I get a bunch of black screen flicker. Any thoughts on how I can set the background color on the textfield when added to the list or fix the screen flicker when I move it out? Appreciate the help.
import SwiftUI
import UIKit
struct ContentView: View {
#Environment(\.managedObjectContext) var managedObjectContext
#FetchRequest(entity: ToDoItem.entity(), sortDescriptors: [NSSortDescriptor(key: "order", ascending: true)]) var listItems: FetchedResults<ToDoItem>
#State private var newToDoItem = ""
#State private var showNewTask = false
#State var isEditing = false
#State var showTaskView = false
#State var bottomState = CGSize.zero
#State var showFull = false
#State var deleteButton = false
//this removes the lines in the list view
init() {
// To remove only extra separators below the list:
UITableView.appearance().tableFooterView = UIView()
// To remove all separators including the actual ones:
UITableView.appearance().separatorStyle = .none
UIScrollView.appearance().backgroundColor = .clear
//UITableView.appearance().backgroundColor = .clear
}
var body: some View {
ZStack{
VStack{
TitleView()
NavigationView {
List {
if showNewTask {
HStack{
TextField("New task", text: self.$newToDoItem, onEditingChanged: { (changed) in
}) {
print("onCommit")
self.addTask(taskTitle: self.newToDoItem)
self.saveTasks()
self.showNewTask.toggle()
self.newToDoItem = ""
}
.font(Font.system(size: 18, weight: .bold))
.foregroundColor(Color("Text"))
Button(action: {
self.newToDoItem = ""
self.showNewTask.toggle()
}) {
Image(systemName: "xmark.circle").foregroundColor(Color("button"))
.font(Font.system(size: 18, weight: .bold))
}
}
.padding(EdgeInsets(top: 8, leading: 6, bottom: 8, trailing: 6))
.background(Color("addNewTask"))
.cornerRadius(10.0)
}
ForEach(listItems, id: \.self) {item in
HStack {
Button(action: {
item.isComplete = true
self.saveTasks()
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5){
self.deleteTaskTest(item: item)
}
}) {
if (item.isComplete) {
Image(systemName: "checkmark.circle")
.font(Font.system(size: 25, weight: .bold))
.foregroundColor(Color(#colorLiteral(red: 0.1616941956, green: 0.9244045403, blue: 0.1405039469, alpha: 1)))
.padding(.trailing, 4)
} else {
Image(systemName: "circle")
.font(Font.system(size: 25, weight: .bold))
.foregroundColor(Color("button"))
.padding(.trailing, 4)
}
}
.buttonStyle(PlainButtonStyle())
ToDoItemView(title: item.title, createdAt: "\(item.createdAt)")
.onTapGesture {
self.showTaskView.toggle()
}
.onLongPressGesture(minimumDuration: 0.1) {
self.isEditing.toggle()
print("this is a long press test")
}
}
.listRowBackground(Color("background"))
}
.onMove(perform: moveItem)
.onDelete(perform: deleteTask)
}
.environment(\.editMode, .constant(self.isEditing ? EditMode.active : EditMode.inactive)).animation(Animation.spring())
.navigationBarTitle(Text("ToDay"), displayMode: .large)
.navigationBarHidden(true)
.background(Color("background"))
}
//ADD A NEW TASK BUTTON
HStack {
Spacer()
Button(action: {
self.showNewTask.toggle()
}) {
Image(systemName: "plus")
.font(.system(size: 18, weight: .bold))
.frame(width: 36, height: 36)
.background(Color("button"))
.foregroundColor(.white)
.clipShape(Circle())
.shadow(color: Color.black.opacity(0.2), radius: 5, x: 0, y: 5)
}
}
.padding()
}
.blur(radius: showTaskView ? 20 : 0)
.animation(.default)
.padding(.top, 30)
//BOTTOM CARD VIEW
TaskView()
.offset(x: 0, y: showTaskView ? 360 : 1000)
.offset(y: bottomState.height)
.animation(.timingCurve(0.2, 0.8, 0.2, 1, duration: 0.5))
.gesture(
DragGesture().onChanged { value in
self.bottomState = value.translation
if self.showFull {
self.bottomState.height += -300
}
if self.bottomState.height < -300 {
self.bottomState.height = -300
}
} .onEnded { value in
if self.bottomState.height > 50 {
self.showTaskView = false
}
if (self.bottomState.height < -100 && !self.showFull) || (self.bottomState.height < -250 && self.showFull){
self.bottomState.height = -300
self.showFull = true
} else {
self.bottomState = .zero
self.showFull = false
}
}
)
}
.background(Color("background").edgesIgnoringSafeArea(.all))
}
Finally got it to work. For whatever reason reworking the stacks fixed it.
struct ContentView: View {
#Environment(\.managedObjectContext) var managedObjectContext
#FetchRequest(entity: ToDoItem.entity(), sortDescriptors: [NSSortDescriptor(key: "order", ascending: true)]) var listItems: FetchedResults<ToDoItem>
#State private var showCancelButton: Bool = false
#State private var newToDoItem = ""
#State private var showNewTask = false
#State var isEditing = false
#State var showTaskView = false
#State var bottomState = CGSize.zero
#State var showFull = false
#State var deleteButton = false
var itemName = ""
init() {
// To remove all separators including the actual ones:
UITableView.appearance().separatorStyle = .none
UITableView.appearance().backgroundColor = .clear
}
var body: some View {
ZStack {
VStack {
NavigationView {
VStack {
TitleView()
.padding(.top, 20)
.background(Color("background"))
// Enter new task view
if showNewTask {
HStack {
HStack {
TextField("New task", text: self.$newToDoItem, onEditingChanged: { (changed) in
}) {
self.addTask(taskTitle: self.newToDoItem)
self.saveTasks()
self.showNewTask.toggle()
self.newToDoItem = ""
}
.font(Font.system(size: 18, weight: .bold))
.foregroundColor(Color("Text"))
Button(action: {
self.newToDoItem = ""
self.showNewTask.toggle()
}) {
Image(systemName: "xmark.circle").foregroundColor(Color("button"))
.font(Font.system(size: 18, weight: .bold))
}
}
.padding(EdgeInsets(top: 8, leading: 6, bottom: 8, trailing: 6))
.background(Color("addNewTask"))
.cornerRadius(10.0)
}
.background(Color("background"))
.padding(.horizontal)
}
List {
ForEach(listItems, id: \.self) {item in
HStack {
Button(action: {
item.isComplete = true
self.saveTasks()
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5){
self.deleteTaskTest(item: item)
}
}) {
if (item.isComplete) {
Image(systemName: "checkmark.circle")
.font(Font.system(size: 25, weight: .bold))
.foregroundColor(Color(#colorLiteral(red: 0.1616941956, green: 0.9244045403, blue: 0.1405039469, alpha: 1)))
.padding(.trailing, 4)
} else {
Image(systemName: "circle")
.font(Font.system(size: 25, weight: .bold))
.foregroundColor(Color("button"))
.padding(.trailing, 4)
}
}
.buttonStyle(PlainButtonStyle())
ToDoItemView(title: item.title, createdAt: "\(item.createdAt)")
.onTapGesture {
//item.title = self.itemName
self.showTaskView.toggle()
}
.onLongPressGesture(minimumDuration: 0.1) {
self.isEditing.toggle()
print("this is a long press test")
}
}
.listRowBackground(Color("background"))
}
.onMove(perform: moveItem)
.onDelete(perform: deleteTask)
}
.environment(\.editMode, .constant(self.isEditing ? EditMode.active : EditMode.inactive)).animation(Animation.spring())
.navigationBarTitle(Text("ToDay"), displayMode: .large)
.navigationBarHidden(true)
.background(Color("background"))
}
.background(Color("background").edgesIgnoringSafeArea(.all))
}
HStack {
Spacer()
Button(action: {
//withAnimation(){
self.showNewTask.toggle()
//}
}) {
Image(systemName: "plus")
.font(.system(size: 18, weight: .bold))
.frame(width: 36, height: 36)
.background(Color("button"))
.foregroundColor(.white)
.clipShape(Circle())
.shadow(color: Color.black.opacity(0.2), radius: 5, x: 0, y: 5)
}
}
.padding()
}
.blur(radius: showTaskView ? 20 : 0)
//BOTTOM CARD VIEW
TaskView()
.offset(x: 0, y: showTaskView ? 360 : 1000)
.offset(y: bottomState.height)
.animation(.timingCurve(0.2, 0.8, 0.2, 1, duration: 0.5))
.gesture(
DragGesture().onChanged { value in
self.bottomState = value.translation
if self.showFull {
self.bottomState.height += -300
}
if self.bottomState.height < -300 {
self.bottomState.height = -300
}
} .onEnded { value in
if self.bottomState.height > 50 {
self.showTaskView = false
}
if (self.bottomState.height < -100 && !self.showFull) || (self.bottomState.height < -250 && self.showFull){
self.bottomState.height = -300
self.showFull = true
} else {
self.bottomState = .zero
self.showFull = false
}
}
)
}
.animation(.default)
.background(Color("background").edgesIgnoringSafeArea(.all))
}
func moveItem(indexSet: IndexSet, destination: Int){
let source = indexSet.first!
if source < destination {
var startIndex = source + 1
let endIndex = destination - 1
var startOrder = listItems[source].order
while startIndex <= endIndex {
listItems[startIndex].order = startOrder
startOrder = startOrder + 1
startIndex = startIndex + 1
}
listItems[source].order = startOrder
} else if destination < source {
var startIndex = destination
let endIndex = source - 1
var startOrder = listItems[destination].order + 1
let newOrder = listItems[destination].order
while startIndex <= endIndex {
listItems[startIndex].order = startOrder
startOrder = startOrder + 1
startIndex = startIndex + 1
}
listItems[source].order = newOrder
}
saveTasks()
self.isEditing.toggle()
}
func deleteTask(indexSet: IndexSet){
let source = indexSet.first!
let listItem = listItems[source]
//self.deleteButton.toggle()
managedObjectContext.delete(listItem)
saveTasks()
}
func deleteTaskTest(item: ToDoItem){
managedObjectContext.delete(item)
saveTasks()
}
func addTask(taskTitle: String) {
let newTask = ToDoItem(context: managedObjectContext)
newTask.title = taskTitle
newTask.order = (listItems.last?.order ?? 0) + 1
newTask.createdAt = Date()
}
func saveTasks() {
do {
try managedObjectContext.save()
} catch {
print(error)
}
}