I'm trying to suss out a problem I'm having. Basically, I have a text field for medical lab values, and I want it to display a symbol when out of range (out of normal medical limits) and another symbol when within normal range. These values are then used in formulae in another view in the app.
This is now my second post on this platform, so please forgive any posting faux pas, I'm trying to adhere to the rules as best as possible re: minimum reproducible and making sure my code is formatted properly in the posting. Here's what I have so far:
import SwiftUI
struct EntryMRE: View {
#Environment(\.managedObjectContext) private var viewContext
#State private var showingResults: Int? = 1
#FocusState private var isTextFieldFocused: Bool
#State var isDone = false
#State var isSaving = false //used to periodically save data
#State var saveInterval: Int = 5 //after how many seconds the data is automatically saved
//DataPoints Chemistry
#State var potassium = ""
var body: some View {
List {
Section(header: Text("🧪 Chemistry")) {
Group {
HStack {
Text("K")
+ Text("+")
.font(.system(size: 15.0))
.baselineOffset(4.0)
Spacer()
TextField("mEq/L", text: $potassium)
.focused($isTextFieldFocused)
.foregroundColor(Color(UIColor.systemBlue))
.modifier(TextFieldClearButton(text: $potassium))
.multilineTextAlignment(.trailing)
.keyboardType(.decimalPad)
if potassium != "" && Int(potassium) != nil {
if Int(potassium)! >= Int(Double(Int(3.5))) && Int(potassium)! <= Int(Double(4.5)) {
Image(systemName: "checkmark.circle.fill")
.foregroundColor(Color(UIColor.systemGreen))
}
else {
Image(systemName: "exclamationmark.circle.fill")
.foregroundColor(Color(UIColor.systemRed))
}
}
}
}
}
}
}
I've tried making it >= Double(3.5) which then pops the same error and says it should be Int(Double(3.5)) which does allow the code to build, but doesn't actually display the symbol when in range with a decimal (ExhibitA), only with a whole integer. (ExhibitB)
I've added some pictures that'll hopefully help show what I mean.
Thanks in advance!
This fails because you are converting a Double to an Int and vice versa and casting your String to an Int instead of a Double. You are loosing your digits when you do this.
Try:
if let numberValue = Double(potassium) { // Cast String to Double
if (3.5...4.5) ~= numberValue { //Check if casted value is in customRange
Image(systemName: "checkmark.circle.fill")
.foregroundColor(Color(UIColor.systemGreen))
}
else {
Image(systemName: "exclamationmark.circle.fill")
.foregroundColor(Color(UIColor.systemRed))
}
}
Related
I'm using SwiftUI Charts to make a LineMark chart. Everything seems to work, except I'm getting this error in the console for every data point that the chart tries to plot.
"Error: this application, or a library it uses, has passed an invalid numeric value (NaN, or not-a-number) to CoreGraphics API and this value is being ignored. Please fix this problem."
I've setup a backtrace in the scheme but I can't make any sense of it.
Here's my code for the chart:
private var chart: some View {
Chart(viewModel.weatherArray, id: \.DATETIME) {
LineMark(
x: .value("Date", $0.DATETIME),
y: .value("Temp", $0.TMP)
)
.lineStyle(StrokeStyle(lineWidth: lineWidth))
.foregroundStyle(chartColor.gradient)
// .interpolationMethod(interpolationMethod.mode)
.symbol(Circle().strokeBorder(lineWidth: lineWidth))
.symbolSize(showSymbols ? 60 : 0)
}
//the chart overlay uses the tap gesture to to define the selected element on the chart
.chartOverlay { proxy in
GeometryReader { geo in
Rectangle().fill(.clear).contentShape(Rectangle())
.gesture(
SpatialTapGesture()
.onEnded { value in
let element = findElement(location: value.location, proxy: proxy, geometry: geo)
if selectedElement?.DATETIME == element?.DATETIME {
// If tapping the same element, clear the selection.
selectedElement = nil
} else {
selectedElement = element
}
}
.exclusively(
before: DragGesture()
.onChanged { value in
selectedElement = findElement(location: value.location, proxy: proxy, geometry: geo)
}
)
)
}
}
.chartBackground { proxy in
ZStack(alignment: .topLeading) {
GeometryReader { geo in
//if showlolipop is the toggle, i dont need that but its simpler to keep it.
if showLollipop,
let selectedElement {
let dateInterval = Calendar.current.dateInterval(of: .hour, for: selectedElement.DATETIME)!
let startPositionX1 = proxy.position(forX: dateInterval.start) ?? 0
let lineX = startPositionX1 + geo[proxy.plotAreaFrame].origin.x
let lineHeight = geo[proxy.plotAreaFrame].maxY
let boxWidth: CGFloat = 100
let boxOffset = max(0, min(geo.size.width - boxWidth, lineX - boxWidth / 2))
//this is the part that defines how the lolipop looks
//lolipop stem
Rectangle()
.fill(.red)
.frame(width: 2, height: lineHeight)
.position(x: lineX, y: lineHeight / 2)
//lolipop head
VStack(alignment: .center) {
Text("\(selectedElement.DATETIME, format: .dateTime.year(.twoDigits).month(.twoDigits).day().hour())")
.font(.callout)
.foregroundStyle(.secondary)
Text("\(selectedElement.TMP, format: .number) °C")
.font(.title2.bold())
.foregroundColor(.primary)
}
//This is the background of the lolipop head
.background {
ZStack {
RoundedRectangle(cornerRadius: 8)
.fill(.background)
RoundedRectangle(cornerRadius: 8)
.fill(.quaternary.opacity(0.7))
}
.padding(.horizontal, -8)
.padding(.vertical, -4)
}
.offset(x: boxOffset)
}
}
}
}
}
//This function is what makes the lolipop work. It finds the info for the labels
func findElement(location: CGPoint, proxy: ChartProxy, geometry: GeometryProxy) -> WeatherForecastClass? {
let data = viewModel.weatherArray
let relativeXPosition = location.x - geometry[proxy.plotAreaFrame].origin.x
if let date = proxy.value(atX: relativeXPosition) as Date? {
// Find the closest date element.
var minDistance: TimeInterval = .infinity
var index: Int? = nil
for weatherDataIndex in data.indices {
let nthWeatherDataDistance = data[weatherDataIndex].DATETIME.distance(to: date)
if abs(nthWeatherDataDistance) < minDistance {
minDistance = abs(nthWeatherDataDistance)
index = weatherDataIndex
}
}
if let index {
return data[index]
}
}
return nil
}
}
Here's the Backtrace:
Backtrace:
<CGPathAddLineToPoint+78>
<$sSo16CGMutablePathRefa12CoreGraphicsE4move2to9transformySo7CGPointV_So17CGAffineTransformVtFTm+113>
<_Charts_getAxisMarkWitnessTable+3021427>
<_Charts_getAxisMarkWitnessTable+3022243>
<_Charts_getAxisMarkWitnessTable+3022291>
<__CGPathApplyWithBlock_block_invoke+63>
<_ZNK2CG4Path5applyEU13block_pointerFv17CGPathElementTypePK7CGPointPbE+246>
<CGPathApplyWithBlock+114>
<_Charts_getAxisMarkWitnessTable+3016835>
<_Charts_getAxisMarkWitnessTable+428626>
<_Charts_getAxisMarkWitnessTable+464967>
<__swift_memcpy33_4+28201>
<_Charts_getAxisMarkWitnessTable+425557>
<_Charts_getAxisMarkWitnessTable+415811>
<_Charts_getAxisMarkWitnessTable+462771>
<_Charts_getAxisMarkWitnessTable+416756>
<_Charts_getAxisMarkWitnessTable+416214>
<_Charts_getAxisMarkWitnessTable+413871>
<_Charts_getAxisMarkWitnessTable+462275>
<_Charts_getAxisMarkWitnessTable+416756>
<_Charts_getAxisMarkWitnessTable+412741>
<_Charts_getAxisMarkWitnessTable+422521>
<_Charts_getAxisMarkWitnessTable+464073>
<_Charts_getAxisMarkWitnessTable+417649>
<_Charts_getAxisMarkWitnessTable+421314>
<_Charts_getAxisMarkWitnessTable+432881>
<_Charts_getAxisMarkWitnessTable+434361>
<_Charts_getAxisMarkWitnessTable+434361>
<_Charts_getAxisMarkWitnessTable+434361>
<_Charts_getAxisMarkWitnessTable+444893>
<_callVisitTableColumnType2+31236>
<__swift_memcpy108_8+1328>
<__swift_memcpy12_4+43911>
<__swift_memcpy12_4+34396>
<_callVisitTableColumnType2+30317>
<block_destroy_helper.6215+63949>
<_ZN2AG5Graph11UpdateStack6updateEv+537>
<_ZN2AG5Graph16update_attributeENS_4data3ptrINS_4NodeEEEj+443>
<_ZN2AG5Graph9value_refENS_11AttributeIDEPK15AGSwiftMetadataRh+123>
<AGGraphGetValue+287>
<__swift_memcpy38_4+28770>
<get_witness_table 7SwiftUI19WidgetConfigurationRzAA13PreferenceKeyRd__r__lAA15Modif
<get_witness_table 7SwiftUI19WidgetConfigurationRzAA13PreferenceKeyRd__r__lAA15Modi
<__swift_memcpy106_8+51272>
<__swift_memcpy106_8+51342>
<-[UIView(CALayerDelegate) layoutSublayersOfLayer:]+2305>
<_ZN2CA5Layer16layout_if_neededEPNS_11TransactionE+526>
<-[UIView(Hierarchy) layoutBelowIfNeeded]+1447>
<__swift_memcpy64_4+166111>
<block_destroy_helper.23+59728>
<block_destroy_helper.23+59750>
<+[UIView(Animation) performWithoutAnimation:]+84>
<__swift_memcpy64_4+161134>
<block_destroy_helper.23+59728>
<block_destroy_helper.23+59750>
<+[UIView(Animation) performWithoutAnimation:]+84>
<__swift_memcpy64_4+166410>
<objectdestroy.142Tm+41107>
<block_destroy_helper+36990>
<block_destroy_helper+35295>
<get_witness_table 7SwiftUI19WidgetConfigurationRzAA13PreferenceKeyRd__r__lAA15M
<__swift_memcpy89_8+19524>
<dynamic_cast_existential_0_superclass_conditional+6323>
<block_destroy_helper.89+25608>
<_callVisitStyleContextType2+33683>
<block_destroy_helper.130+8663>
<block_destroy_helper.130+8519>
<-[UIViewController _setViewAppearState:isAnimating:]+1061>
<-[UIViewController __viewDidAppear:]+146>
<-[UINavigationController viewDidAppear:]+173>
<-[UIViewController _setViewAppearState:isAnimating:]+1061>
<__52-[UIViewController _setViewAppearState:isAnimating:]_block_invoke_2+189>
<__52-[UIViewController _setViewAppearState:isAnimating:]_block_invoke+185>
<__NSARRAY_IS_CALLING_OUT_TO_A_BLOCK__+7>
<-[__NSSingleObjectArrayI enumerateObjectsWithOptions:usingBlock:]+80>
<-[UIViewController _setViewAppearState:isAnimating:]+2066>
<__52-[UIViewController _setViewAppearState:isAnimating:]_block_invoke_2+189>
<__52-[UIViewController _setViewAppearState:isAnimating:]_block_invoke+185>
<__NSARRAY_IS_CALLING_OUT_TO_A_BLOCK__+7>
<-[__NSSingleObjectArrayI enumerateObjectsWithOptions:usingBlock:]+80>
<-[UIViewController _setViewAppearState:isAnimating:]+2066>
<-[UIViewController __viewDidAppear:]+146>
<-[UINavigationController viewDidAppear:]+173>
<-[UIViewController _setViewAppearState:isAnimating:]+1061>
<-[UIViewController __viewDidAppear:]+146>
<-[UIViewController _endAppearanceTransition:]+232>
<-[UINavigationController navigationTransitionView:didEndTransition:fromView:toV
<__49-[UINavigationController _startCustomTransition:]_block_invoke+620>
<-[_UIViewControllerTransitionContext completeTransition:]+101>
<__53-[_UINavigationParallaxTransition animateTransition:]_block_invoke.163+841>
<__UIVIEW_IS_EXECUTING_ANIMATION_COMPLETION_BLOCK__+15>
<-[UIViewAnimationBlockDelegate _didEndBlockAnimation:finished:context:]+797>
<-[UIViewAnimationState sendDelegateAnimationDidStop:finished:]+190>
<-[UIViewAnimationState animationDidStop:finished:]+263>
<-[UIViewAnimationState animationDidStop:finished:]+648>
<_ZN2CA5Layer23run_animation_callbacksEPv+318>
<_dispatch_client_callout+8>
<_dispatch_main_queue_drain+1463>
<_dispatch_main_queue_callback_4CF+31>
<__CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__+9>
<__CFRunLoopRun+2482>
<CFRunLoopRunSpecific+560>
<GSEventRunModal+139>
<-[UIApplication _run]+994>
<UIApplicationMain+123>
<__swift_memcpy93_8+11936>
<__swift_memcpy93_8+11597>
<__swift_memcpy195_8+12255>
<$s27SpotWx_MergeBackendFrontend0ab1_cdE3AppV5$mainyyFZ+30>
<main+9>
1079162bf
[Unknown process name] CGPathCloseSubpath: no current point.
I think I've narrowed the problem down to the interactivity of the chart. When I simplify the chart down to this the errors go away:
private var chart: some View {
Chart(viewModel.weatherArray, id: \.DATETIME) {
LineMark(
x: .value("Date", $0.DATETIME),
y: .value("Temp", $0.TMP)
)
}
}
I've managed to deduce the problem was this modifier on the Chart element:
.symbolSize(showSymbols ? 60 : 0)
I'm not sure why this happens, as that modifier worked when I was testing it on a static array of data, although all that data was positive. If someone knows what the root of the problem is I'm pretty curious.
I am working on a SwiftUI app (Xcode Version 12.4 and iOS 14.4.2) and am having problems to properly handle buttons in a list. I hope someone can point out the way to go.
Here is the relevant code:
struct CustomListView: View {
var localList:[SomeManagedObject], moc:NSManagedObjectContext
#State var showingOtherView = false
#State var selectNbr:Int!
func handleCustomItem(_ argument: SomeManagedObject) {
print(#function+" (1):\(showingOtherView):")
selectNbr = localList.firstIndex(of: argument)
self.showingOtherView.toggle()
print(#function+" (2):\(showingOtherView):\(selectNbr):")
..... Do useful things .....
}
var body: some View {
List {
ForEach(self.localList) {
item in
HStack {
Spacer()
Button(action: {
self.handleCustomItem(item)
})
{
Text(item.expression!)
.foregroundColor(Color.red))
.font(.headline)
.padding(.horizontal, 11).padding(.vertical, 15)
}
Spacer()
}
}
}.sheet(isPresented: $showingOtherView) {
OtherView(parameter: localList[selectNbr])
}
}
}
When I run the app this is what I get as ouput, which is what I expect:
handleCustomItem(_:) (1):false:
handleCustomItem(_:) (2):true:Optional(4):
Then I have a break point before running this line of code (to avoid a crash):
OtherView(parameter: localList[selectNbr])
This is where things get weird (unexpected for me), in the debugger I can see:
(lldb) p showingOtherView
(Bool) $R0 = false
(lldb) p selectNbr
(Int?) $R2 = nil
(lldb)
But I would expect showingOtherView to be true, and (more important) selectNbr to hold the value 4. What is going on here that I am missing ?
the SwiftUI App I'm writing has large blocks of text, so I'm trying to create a "markup language" I can use in my JSON files that hold the text, to define when a word should be bolded. The issue is, I can't seem to stop new text blocks going to a different line.
Here is the relevant code as I currently have it.
struct formattedText: View {
var text: String
var body: some View {
let split = text.components(separatedBy: "**")
Group {
ForEach(split, id: \.self) { line in
if line.hasPrefix("$$") {
Text(line.trimmingCharacters(in: CharacterSet(charactersIn: "$")))
.bold()
} else {
Text(line)
}
}
}
.lineLimit(nil)
.multilineTextAlignment(.leading)
}
Using this code, I can put a **$$ before a word and ** after, to define it as bold.
Only issue is every time I bold a word it goes to a new line. I know the traditional way to fix this is:
Text("Simple ") + Text("Swift ") + Text("Guide")
This does not work with my ForEach loop though. Any suggestions?
ForEach creates separate Views. You really just want one Text, so you mean a for...in loop:
var body: some View {
let split = text.components(separatedBy: "**")
var result = Text("")
for line in split {
if line.hasPrefix("$$") {
result = result + Text(line.trimmingCharacters(in: CharacterSet(charactersIn: "$")))
.bold()
} else {
result = result + Text(line)
}
}
return result
.lineLimit(nil)
.multilineTextAlignment(.leading)
}
Since you may want a lot more things than just bold, you might find it useful to extract that part into its own function or collection of functions:
private func applyAttributes(line: String) -> Text {
if line.hasPrefix("$$") {
return Text(line.trimmingCharacters(in: CharacterSet(charactersIn: "$")))
.bold()
} else {
return Text(line)
}
}
With that, constructing this is simpler:
var body: some View {
text.components(separatedBy: "**")
.map(applyAttributes)
.reduce(Text(""), +)
.lineLimit(nil)
.multilineTextAlignment(.leading)
}
I am missing something in my understanding of SwiftUI State variables.
I have a Picker and I want to apply the value from the Picker to another State var.
Specifically I want to use a Picker to spect a MassUnit and apply the new MassUnit to a Measurement. When the MassUnit is selected, the measurement uses .convert or .converted to change the Measurement to the new unit.
If you look at the code below, the value of converted is correctly calculated but I can’t set max to the new value and I can't use max.convert to change it.
What am I doing wrong? (I've tried applying a Binding var but that wasn't working. My understanding is that this view should be able to mutate this state property.)
import SwiftUI
struct ConverterView: View
{
#State var massUnit: UnitMass = .pounds
#State var min: Measurement<UnitMass> = Measurement(value: 1, unit: .pounds)
#State var max: Measurement<UnitMass> = Measurement(value: 1.5, unit: .pounds)
private let massFormatter = MewsMassFormatter()
private let unitFormatter = MewsMassFormatter(style: .long)
var body: some View {
Form {
Picker(selection: $massUnit,
label: Text("Weight Units"),
content: {
ForEach(massUnits, id: \.symbol) {
Text(unitFormatter.string(from: $0)).tag($0)
}
})
.onChange(of: $massUnit.wrappedValue, perform: { newMassUnit in
let converted = $max.wrappedValue.converted(to: newMassUnit)
$max.wrappedValue = converted //doesn't work
$max.wrappedValue.value = converted.value //doesn't work
// $max.wrappedValue.unit = converted.unit //doesn't compile
$max.wrappedValue.convert(to: newMassUnit) //doesn't work
})
TextField("Min", value: $min, formatter: massFormatter).keyboardType(.decimalPad)
TextField("Max", value: $max, formatter: massFormatter).keyboardType(.decimalPad)
Text(unitFormatter.string(from: $massUnit.wrappedValue))
}
}
}
struct ConverterView_Previews: PreviewProvider {
static var previews: some View {
ConverterView()
}
}
No need $ sign and wrapped value. you can directly assign.
.onChange(of: massUnit, perform: { newMassUnit in
let converted = max.converted(to: newMassUnit)
max = converted
max.convert(to: newMassUnit)
})
And,
Text(unitFormatter.string(from: massUnit))
I'm trying to run through a list of items that all have a field called "yes" which is an integer. Essentially this loop runs through and if the yes field for an item is more than 0, it will show up, and if it is 0 it won't. Works as I want it to so far but what I would like to do is show a different message if ALL items have a 0 value, so that the section of the screen is not simply empty. How would I go about doing this? I tried putting the message into the "else" but (obviously) it just repeated the message the amount of times there are items in the db.
ForEach(items.indices, id: \.self) { i in
if (items[i].yes != 0) {
HStack {
Text(items[i].name)
Spacer()
Text("\(items[i].yes)")
}
Divider()
} else {}
}
You need something like below (typed in place, so might be typos):
if items.filter({ $0.yes != 0}).isEmpty {
Text("Message for ALL are 0")
} else {
ForEach(items.indices, id: \.self) { i in
if (items[i].yes != 0) {
HStack {
Text(items[i].name)
Spacer()
Text("\(items[i].yes)")
}
Divider()
} else {}
}
}