Hi all,
I am working on my first app and I am close to publish it. I am working on fixing some bugs currently. I have been trying to solve this bug for a while now with no luck. When in the digesters view page and showing the Site header the title overlaps when pulling down the digester card, also when scrolling up the card the header moves up a bit and the title disappears. This doesn’t happen with the digester header despite the code being as similar as possible. Can someone help please. Thanks.
import SwiftUI
struct SiteHeaderView: View {
var showHeader: Bool
var site: Site
var body: some View {
if showHeader {
//Page Header
HStack{
Image("adsite")
.resizable()
.aspectRatio(contentMode: .fit)
.frame(width: 160, height: 160)
VStack(alignment: .leading, spacing: 10) {
Text("Digesters: \(site.digesters.count)")
Text("Company: \(site.company)")
}
}
.padding(.horizontal, 30)
.background{
RoundedRectangle(cornerRadius: 15)
.fill(.headerBg)
.ignoresSafeArea()
}
}
}
}
import SwiftUI
import SwiftData
import TelemetryClient
struct DigestersView: View {
@Environment(\.dismiss) private var dismiss
@Environment(\.modelContext) private var context
@Bindable var defaultElement: DefaultElement
@State private var newDigester: Digester?
@State private var selectedDigester: Digester?
@State private var editDigester: Digester?
@State private var deleteDigester: Digester?
@State private var showDeleteConfirmation = false
@State private var isEditMode = false
@Bindable var site: Site
var body: some View {
NavigationStack{
let showHeader = UserDefaults.standard.bool(forKey: "siteHeader")
SiteHeaderView(showHeader: showHeader, site: site)
//Sort Digesters by the tank number from 1 to 5
let sortedDigesters = site.digesters.sorted(by: { u1, u2 in
u1.tankNumber < u2.tankNumber
})
if site.digesters.count == 0 {
Spacer()
Image("digester.trianglebadge.exclamationmark")
.symbolRenderingMode(.palette)
.foregroundStyle(.red, .gray)
.font(.system(size: 100))
Text("No digesters in the list")
.font(.headline)
Text("Add at least one digester")
.foregroundStyle(.gray)
.font(.subheadline)
Button("Add Digester") {
isEditMode = false
newDigester = Digester()
}
.buttonStyle(.bordered)
.padding()
Spacer()
}
else {
List (sortedDigesters) {digester in
DigesterCardView(digester: digester)
.id(refreshList)
.onTapGesture {
selectedDigester = digester
}
.swipeActions(edge: .trailing, allowsFullSwipe: false) {
Button (role: .destructive) {
deleteDigester = digester
showDeleteConfirmation = true
} label: {
Label("Delete" ,systemImage: "trash")
}
}
.swipeActions(edge: .leading, allowsFullSwipe: false) {
Button (role: nil) {
editDigester = digester
isEditMode = true
} label: {
Label("Edit" , systemImage: "pencil")
}
.tint(.yellow)
}
.listRowSeparator(.hidden)
}
.scrollIndicators(.hidden)
.listStyle(.plain)
}
}
.navigationTitle(site.siteName)
.navigationDestination(item: $selectedDigester) { digester in
LabResultsView(digester: digester)
}
.toolbar {
ToolbarItem(placement: .topBarTrailing) {
Button {
isEditMode = false
newDigester = Digester()
} label: {
Image("digester.badge.plus")
.symbolRenderingMode(.palette)
.foregroundStyle(.green, .gray)
.font(.system(size: 20))
}
}
}
.confirmationDialog("Really delete?", isPresented: $showDeleteConfirmation, titleVisibility: .visible) {
Button("Yes, delete it", role: .destructive) {
withAnimation {
//Delete data record from SfiftData
context.delete(deleteDigester!)
//Refresh List
refreshList.toggle()
//Force swiftdate save
try? context.save()
TelemetryDeck.signal("Digester", parameters: ["Status" : "Deleted"])
}
}
}
.sheet(item: $newDigester) { digester in
AddEditDigesterView(site: site, digester: digester, defaultElement: defaultElement, isEditMode: isEditMode)
}
.sheet(item: $editDigester) { digester in
AddEditDigesterView(site: site, digester: digester, defaultElement: defaultElement, isEditMode: isEditMode)
}
}
}
import SwiftUI
struct DigesterHeaderView: View {
var showHeader: Bool
var digester: Digester
@State private var showTarget: Bool = false
var body: some View {
if showHeader {
//Page Header
HStack{
ZStack{
Image("digester")
.symbolRenderingMode(.monochrome)
.foregroundStyle(.gray)
.font(.system(size: 100))
VStack{
Text(digester.digesterType)
.font(.subheadline)
.foregroundStyle(.gray)
.padding(.top, 60)
Text("\(digester.tankVolume) m3")
.font(.subheadline)
.foregroundStyle(.gray)
.padding(.bottom)
}
}
Spacer()
VStack(alignment: .leading, spacing: 10) {
Text("Lab Samples: \(digester.sampleResults.count)")
Text("Dosing Required: \(digester.doseCount)")
Toggle("Show Element Target", isOn: $showTarget)
if showTarget {
//Show Custom or Default Targets
Text("Target Type: \(digester.defaultElement ? "Default" : "Custom")")
//Nickel
let nickelFormatedAverage = digester.defaultElement ? String(format: "%.2f", digester.defaultNickelAverage) : String(format: "%.2f", digester.nickelAverage)
Text(String("Nickel: \(nickelFormatedAverage) g/m3 FM"))
//Cobalt
let cobaltFormatedAverage = digester.defaultElement ? String(format: "%.2f", digester.defaultCobaltAverage) : String(format: "%.2f", digester.cobaltAverage)
Text(String("Cobalt: \(cobaltFormatedAverage) g/m3 FM"))
//Molybdenum
let molybdenumFormatedAverage = digester.defaultElement ? String(format: "%.2f", digester.defaultMolybdenumAverage) : String(format: "%.2f", digester.molybdenumAverage)
Text(String("Molybdenum: \(molybdenumFormatedAverage) g/m3 FM"))
//Selenium
let seleniumFormatedAverage = digester.defaultElement ? String(format: "%.2f", digester.defaultSeleniumAverage) : String(format: "%.2f", digester.seleniumAverage)
Text(String("Selenium: \(seleniumFormatedAverage) g/m3 FM"))
}
}
}
.padding()
.background{
RoundedRectangle(cornerRadius: 15)
.fill(.headerBg)
.ignoresSafeArea()
}
}
}
}
import SwiftUI
import SwiftData
import TelemetryClient
struct LabResultsView: View {
@Environment(\.dismiss) private var dismiss
@Environment(\.modelContext) private var context
@Bindable var digester: Digester
@State private var newLabResults: LabResults?
@State private var deleteResult: LabResults?
@State private var showDeleteConfirmation = false
@State private var editResult: LabResults?
@State private var selectedLabResults: LabResults?
var body: some View {
NavigationStack{
let showHeader = UserDefaults.standard.bool(forKey: "digesterHeader")
DigesterHeaderView(showHeader: showHeader, digester: digester)
//Sort sample results by sampling date. Newest on top
let sortedResults = digester.sampleResults.sorted(by: { u1, u2 in
u1.sampleDate > u2.sampleDate
})
if digester.sampleResults.count == 0 {
Spacer()
Image("custom.flask.trianglebadge.exclamationmark")
.symbolRenderingMode(.palette)
.foregroundStyle(.red, .gray)
.font(.system(size: 100))
Text("No sample results in the list")
.font(.headline)
Text("Add at least one sample result")
.foregroundStyle(.gray)
.font(.subheadline)
Button("Add Sample Result") {
newLabResults = LabResults()
}
.buttonStyle(.bordered)
.padding()
Spacer()
}
else {
List(sortedResults) {result in
SaltRequiredCardView(results: result, digester: digester, doseButton: true, dates: true, showOverdoseValue: false)
.onTapGesture{
selectedLabResults = result
}
.id(refreshList)
.swipeActions(edge: .trailing, allowsFullSwipe: false) {
Button (role: .destructive) {
deleteResult = result
showDeleteConfirmation = true
} label: {
Label("Delete" ,systemImage: "trash")
}
}
.swipeActions(edge: .leading, allowsFullSwipe: false) {
Button (role: .none) {
editResult = result
} label: {
Label("Edit" , systemImage: "pencil")
}
.tint(result.dosed ? .gray.opacity(0.1) : .yellow)
.disabled(result.dosed)
}
.listRowSeparator(.hidden)
}
.scrollIndicators(.hidden)
.listStyle(.plain)
}
}
.navigationTitle("Digester \(digester.tankNumber)")
.navigationDestination(item: $selectedLabResults) { result in
LabResultDetailView(results: result, digester: digester)
}
.toolbar {
ToolbarItem(placement: .topBarTrailing) {
Button {
newLabResults = LabResults()
} label: {
Image("custom.flask.badge.plus")
.symbolRenderingMode(.palette)
.foregroundStyle(.green, .gray)
.font(.system(size: 20))
}
}
}
.sheet(item: $newLabResults) { results in
AddEditLabResults(digester: digester, labResults: results, isEditMode: false)
}
.confirmationDialog("Really delete?", isPresented: $showDeleteConfirmation, titleVisibility: .visible) {
Button("Yes, delete it", role: .destructive) {
withAnimation {
if !deleteResult!.dosed && !isOverdosed() && !isElementZero() && dosingRequired() {
digester.doseCount -= 1
}
//Delete data record from SfiftData
context.delete(deleteResult!)
//Refresh List
refreshList.toggle()
//Force swiftdate save
try? context.save()
TelemetryDeck.signal("labResults", parameters: ["Status" : "Deleted"])
}
}
}
.sheet(item: $editResult) { result in
AddEditLabResults(digester: digester, labResults: result, isEditMode: true)
}
}
private func isOverdosed() -> Bool {
return deleteResult!.cobaltOverdose && deleteResult!.seleniuOverdose && deleteResult!.molybdenumOverdose && deleteResult!.nickelOverdose
}
private func isElementZero() -> Bool {
return Double(deleteResult!.nickelRequired) == 0.0 && Double(deleteResult!.cobaltRequired) == 0.0 && Double(deleteResult!.molybdenumRequired) == 0.0 && Double(deleteResult!.seleniumRequired) == 0.0
}
private func dosingRequired() -> Bool {
return Double(deleteResult!.nickelRequired)! > 0 || Double(deleteResult!.cobaltRequired)! > 0 || Double(deleteResult!.molybdenumRequired)! > 0 || Double(deleteResult!.seleniumRequired)! > 0
}
}
