I want to make an app that has 2 textfields, 4 buttons and a Text view. 1 button that clears the content of the first textfield and another button that inputs the current time rounded to the nearest 4 and the Text view to display the total time in minutes. Same with the other textfield. It's an app to calculate the total time spent on something in minutes.
I also want to limit the amount of possible insertable characters to 5 as a time has a maximum of 5 characters as in "15:00". I have been able to make an observable object:
class TextFieldManager: ObservableObject {
let characterLimit = 5
#Published var userInput = "" {
didSet {
// Limit the max length of the field
if userInput.count > characterLimit {
userInput = String(userInput.prefix(characterLimit))
} else if userInput.count >= 4 {
// call the setTotalTime func or something to set totalTime
}
}
}
}
which works really well.
I am also able to clear the textfield and set the current time rounded to the nearest 5, but i am unable to limit the characters to 5 AND have the buttons do something at the same time.
So right now it's either being able to:
- Limit characters to a maximum of 5
OR being able to:
clear textfield with a button
input time now rounded to nearest 5 into textfield with a button.
If i go with the observable object method, when i have this above the body block of my ContentView:
#ObservedObject var startTimeManager = TextFieldManager()
#ObservedObject var endTimeManager = TextFieldManager()
and this inside the body for the textfields:
TextField("hh:mm", text: $startTimeManager.userInput)
TextField("hh:mm", text: $endTimeManager.userInput)
and the TextFieldManager class as shown above, than now i don't know how to get the value of the Textfield anymore.
If inside the input the current time button i try to set the time by doing
$startTimeManager.userInput = "whatever the current time is"
i get an error saying that i can't change the value of a binding<String> type something something. Likewise i also can't clear the textfield in the same way.
Also i would like to call a function inside this part:
} else if userInput.count >= characterLimit {
// call the setTotalTime func or something to set totalTime
}
I have a Functions.swift file where both the TextFieldManager class is and my function that i want to call, but if i try to call a function inside here, it says that the function doesn't exist? And inside the function i again would like to have access to the textfield values at the time of the call, but i don't know how to read the value of the textfields from inside the function.
I hope i am making sense and that someone is able to help me, or point me in the right direction. I made the same app for android (android studio), windows (python3) and Mac (python3), but this iphone and Xcode thing really doesn't want to work. I have watched a bunch of tutorial videos and guides, but none are trying to do what i am trying to do. Like i said i can get either to work, but never together and in both cases i am unable to somehow access the textfield values inside the function. I feel like i should be so close, but something is not coming together for me in my head.
Also while i'm at it, in Python 3 to catch all errors and let them pass silently i can do:
Try:
break_my_stuff = int("Break my stuff)"
ignore_some_more_stuff = int("ignore some more stuff)"
etc.
etc.
except Exception:
# catch silently and do nothing
pass
Is there something similar in swift, because
do {
breakMyStuff = whatever might make something break in swift
ignoreSomeMoreStuff = whatever might make something break in swift
etc.
etc.
} catch {
// do nothing and pass silently
}
doesn't work because it needs something to try and i wasn't able to try a complete section like i can with Python.
If inside the input the current time button i try to set the time by doing
$startTimeManager.userInput = "whatever the current time is"
you don't need binding in this case, just assign property in regular way
self.startTimeManager.userInput = "whatever the current time is"
Related
After clicking the "Return" action on a selected item from a completed Purchase Receipt, we're trying to get the "Open PO Line" value to default to false. Anyone know how customize this?
The field defaulting seems to be overwritten when we press the "Return" button. We've tried several different events in the grid but none of the seem to work.
The desired result is to default the "Open PO Line" to false after a return and once the return is released the Purchase Order line associated with the return should remain completed.
Research
The AllowOpen field on POReceiptLine is a PXBool. This means that the value must be populated via a PXDBScalar, PXFormula, etc. or via some business logic in the graph. To see what is happening, let's look at the DAC for POReceiptLine...
#region AllowOpen
public abstract class allowOpen : PX.Data.BQL.BqlBool.Field<allowOpen> { }
protected Boolean? _AllowOpen;
[PXBool()]
[PXUIField(DisplayName = "Open PO Line", Visibility = PXUIVisibility.Service, Visible = true)]
public virtual Boolean? AllowOpen
{
get
{
return this._AllowOpen;
}
set
{
this._AllowOpen = value;
}
}
#endregion
As you can see, there isn't any logic to this field in the DAC, so we need to turn to the graph to see how it is used. (Even if there were logic in the DAC, we would need to see if the graph does something. However, logic in the DAC might have been an easy override with CacheAttached - unfortunately, not in this case.)
Let's turn to POReceiptEntry where the return is handled. We find AllowComplete and AllowOpen being set in the POReceiptLine_RowSelected event, as we would expect since it must be populated on the graph side of code having no logic in the DAC.
if ((row.AllowComplete == null || row.AllowOpen == null) && fromPO)
{
POLineR source = PXSelect<POLineR,
Where<POLineR.orderType, Equal<Required<POLineR.orderType>>,
And<POLineR.orderNbr, Equal<Required<POLineR.orderNbr>>,
And<POLineR.lineNbr, Equal<Required<POLineR.lineNbr>>>>>>
.Select(this, row.POType, row.PONbr, row.POLineNbr);
// Acuminator disable once PX1047 RowChangesInEventHandlersForbiddenForArgs [Legacy, moved the exception here from PX.Objects.acuminator because the condition was changed]
row.AllowComplete = row.AllowOpen = (row.Released == true) ?
(row.ReceiptType != POReceiptType.POReturn ? source?.Completed == true : source?.Completed != true) :
(source?.AllowComplete ?? false);
The field is populated in the row.AllowComplete = row.AllowOpen = (row.Released == true) ?... section of code.
Subsequently, we see that the CopyFromOrigReceiptLine method sets this value to false on the "destLine" being created.
destLine.AllowOpen = false;
As that isn't "true" then we know this isn't our spot. Continuing on, we see in UpdatePOLineCompletedFlag that AllowComplete and AllowOpen are being set. This could be our spot (or one of them).
row.AllowComplete = row.AllowOpen = poLineCurrent.AllowComplete;
Side note: It is worth noting that this line appears twice in an if then else. In both cases it is going to be executed, therefore it would be better coding practice to place this identical statement AFTER the if then else since both the if and else conditions will execute this same statement regardless of the if.
This particular use appears to be pulling the value from the AllowComplete field of the PO Line being received. At this point, you should consider if you need to look upstream at the PO Line to see if the field there needs to be manipulated as well. I cannot answer that for you as your business case will drive the decision.
There also is a line in the Copy method that sets AllowComplete and AllowOpen.
aDest.AllowComplete = aDest.AllowOpen = (aSrc.CompletePOLine == CompletePOLineTypes.Quantity);
The Copy method is overloaded, and the other signature of the method sets the values to true.
aDest.AllowComplete = true;
aDest.AllowOpen = true;
Both of these cases may need customization as well, but I don't think it's the primary issue.
Next Steps
At this point, we see that either the field is being set in UpdatePOLineCompletedFlag or in methods that seem related to copying records. You will need to investigate further if the copy related methods warrant a change as well. However, I think the initial focus should be on the UpdatePOLineCompletedFlag method.
If we find the other points identified require customization, we likely will handle them all the same way... Override the base method/event, invoke the original method/event in our override, and then force the values to fit our business case. Careful testing will be needed since forcibly altering these values may create unforeseen negative ripples.
Something to try
We want to update (or create) a graph extension for POReceiptEntry to override the UpdatePOLineCompleteFlag method. This compiles, but it is completely untested on my part. We need to create a delegate and specify the PXOverride attribute. Then we want to execute the base method before we override the field(s) in question.
Note the extra code (commented out) as a reminder that you need to be careful of methods (typically events) updating our record in the cache and needing to be located so that we don't use a stale copy of the record. I don't think that's necessary in this case, but it seems to be somewhat obscure in code samples that I see. Of course, that is because I'm always looking at the code repository which rarely has graph extensions overriding event handlers!
#region CreateReceiptEntry Override
public delegate void UpdatePOLineCompleteFlagDelegate(POReceiptLine row, bool isDeleted, POLine aOriginLine);
[PXOverride]
public virtual void UpdatePOLineCompleteFlag(POReceiptLine row, bool isDeleted, POLine aOriginLine, UpdatePOLineCompleteFlagDelegate baseMethod)
{
//Execute original logic
baseMethod(row, isDeleted, aOriginLine);
/* If the base method has updated the cache, then we would need to locate the updated record in the cache to proceed
* This tends to be the case more often with event handlers, so it probably isn't needed in this case.
* This is just for reference as a training reminder
//If row has been updatd in the baseMethod, let's go get the updated cache values
POReceiptLine locateRow = Base.transactions.Locate(row);
if (locateRow != null) row = locateRow;
*/
//Override the fields to false - need to test to see if this creates any issues with breaking existing business logic
row.AllowComplete = row.AllowOpen = false;
}
#endregion
If this doesn't get you the specific answer you need, I hope it at least gives you some insight into how to hunt down "the spot" to change. I suspect you may need to update the POLine for a complete solution as hinted above. (See the event handler POReceiptLine_AllowOpen_FieldUpdated for the code that leads me to that conclusion.)
Good luck with your customization, and happy coding!
I'm trying to take a set of names with check boxes next to them and make a system so that you can check some of the names (mark them as "True") and click a button. It would then increment +1 the value next to the names of the people marked true.
Here is a link to a sample sheet:
https://docs.google.com/spreadsheets/d/1gf-BrXXR0cAYCn7bMkvvK65R290NXbP9D6aA68c06C8/edit?usp=sharing
If column A, row 2 (Tim's row) is marked true, I want to increment the value in column C, row 2 by one, so Tim would have a running total of tardies next to his name.
I hope this is do-able. Thanks!
(Now I know what you're trying to get)
In order to increment a value via the press of a button, as far as I know you have to use scripts (Tools -> Script Editor). Here's something I threw together:
// editCell takes the cell to edit and it's new value
function editCell(cellName, value) {
SpreadsheetApp.getActiveSheet().getRange(cellName).setValue(value);
}
// getCell takes the cell's value and returns it
function getCell(cellName) {
return SpreadsheetApp.getActiveSheet().getRange(cellName).getValue();
}
// plusOne adds one to the field supplied. It's linked to the button in the sheet
function plusOne() {
editCell("C2",getCell("C2")+1);
}
In order to make it work, you may need to change the targeted Cell (currently C2). You'll also need to create a drawing (Insert -> Drawing) which will act as the button you'll be able to press. Once inserted, click on the three dots on it and click on Link Script. Type in plusOne. When executing it the first time, it'll ask you to authenticate the use of scripts.
That should do the trick. I hope you have some understanding of Java Script though (to modify the code to your needs optimally).
Edit - Expandable version
So, to make every number behind a ticked field increase by one, you can use this version of the code:
// Adds one to every field within "AddArea" that has a tick in front of it. It's linked to the button in the sheet.
function plusOne() {
var ss = SpreadsheetApp.getActiveSheet();
var range = ss.getRange("AddArea");
var values = range.getValues();
var newValues = [];
for (var i = 0; i < range.getNumRows(); ++i) {
var row = values[i];
if(row[0]) {
newValues.push([true, row[1]+1]);
}
else {
newValues.push([false, row[1]]);
}
}
range.setValues(newValues);
}
You need to define a custom named area, named "AddArea" (Data -> Labeled Areas [or similar]), link the script to a button and allow the script to be run. This was hard but very fun to figure out.
Example Sheet for reference (updated)
Can be achieved with just, for example for C2:
=A2+C2
but you would need to turn on iterative calculation (File > Spreadsheet settings... > Calculation [Max. 1 is adequate]) and I would not really recommend that over a trigger with Google Apps Script.
hello i need some help debugging this simple script i compiled to TRY and reset cells F3 all the way down to F338 in google spreadsheets.
function ClearRange()
{
if('H20' => 1)
{
sheet.getRange('F3:F338').clearContent();
sheet.clearcontent('H20');
}
}
more or less i want it to run on edit, if on edit cell H20 is greater then one (would be awesome if it was anything other then blank but im unsure how to do that) then it would set the range to blank cells. i would then like it to reset cell H20 back to blank or 0 then end the script.
To return the value of a cell, use something like this:
var sheet = SpreadsheetApp.getActiveSheet();
var cellValue = sheet.getRange('H20').getValue();
You need to manually declare the sheet variable if you want to use .getRange().getValue() or .getDataRange() and a bunch of other stuff.
Also note that just typing in 'H20' doesn't return the cell value, it's just interpreted as a string of text.
To detect if H20 is anything other than blank, you could test if the cell value's length is not 0:
if(sheet.getRange('H20').getValue().length != 0)
To also do a .clearContent() on cell H20, you'll need to do the same thing you did with cells F3:F338:
sheet.getRange('H20').clearContent();
To make the function run on an edit, click on the clock-like icon in the script editor, next to the save icon, and create a new trigger for ClearRange.
I understand how I could, for example look at one element in a table, but what's the correct way to simply iterate through all the elements, however many are found, to for example tap on them?
let indexFromTheQuery = tables.staticTexts.elementBoundByIndex(2)
Okay I succeeded in figuring out the simple syntax. I have just started working with Swift and so it took me sleeping on it to think of the answer.
This code works:
var elementLabels = [String]()
for i in 0..<tables.staticTexts.count {
elementLabels.append (tables.staticTexts.elementBoundByIndex(i).label)
}
print (elementLabels)
My guess is the answer is there is no way to do so.
The reason is because of my experience with one of the critical iOS components while making a number of UI tests: UIDatePicker.
If you record a test where you get the page up and then you spin the picker, you will notice that the commands are all non-specific and are screen related. In the case of the picker, however, community requests resulted in the addition of a method for doing tests: How to select a picker view item in an iOS UI test in Xcode?.
Maybe you can add a helper method to whatever controller contains this table. Also, keep in mind that you can easily add methods without polluting the class interface by defining them as extensions that are in test scope only.
For Xcode 11.2.1, SwiftUI and swift 5 based app, the following code works for testing a list, each element in this case appears as a button in the test code. The table is set up like this (for each row) :
NavigationLink(destination: TopicDetail(name: "Topic name", longDesc: "A description")) {
TopicRow(thisTopic: top).accessibility(identifier: "newTopicRow_\(top.name!)")
}
Then I catch the members of the table by getting the buttons into an array:
let myTable = app.tables.matching(identifier: "newTopicTable")
var elementLabels = [String]()
for i in 0..<myTable.buttons.count {
elementLabels.append (tablesQuery.buttons.element(boundBy: i).label)
}
print (elementLabels)
Finally, I deleted each member of the table by selecting the detail view where I have a delete button, again with
.accessibility(identifier: "deleteTopic"
I wanted to delete all members of the table:
for topicLabel in elementLabels {
let myButton = app.buttons[topicLabel]
myButton.firstMatch.tap()
app.buttons["deleteTopic"].tap()
}
I am relatively new to swift. I made a simple text-based calculator in swift using NSExpression. I have a problem, however: this text-based calculator calculates the first expression typed into it perfectly every time (expressions from 2+4 to 9*7-1/3*2+1), but the second time, the outputted result is always just the same result as the first one (it won't calculate again). Here is the basics of my code:
I call it from a view controller and make a result label's text equal to the result found in the swift file far below:
x.savetextfinal = calculatortextfield.text! // Variable x.savetextfinal, created elsewhere in a struct, is assigned to the user's input.
// Now the magic happens in calculatormath.swift (see below)
print("this is the result \(z.nsexpress2)")
equalsresultlabel.text = "Result is (z.nsexpress2)."
Then this block of code, found in a separate swift file, seems to me to occur. It uses NSExpression to convert the expression from a string to an NSExpression, then creates a variable and assigns the result "result" of the NSExpression "mathExpression" to it as a string.
import Foundation
var nsexpress:String = String(x.savetextfinal)
var mathExpression = NSExpression(format: nsexpress)
var result:Float = Float((mathExpression.expressionValueWithObject(nil, context: nil) as? NSNumber)!)
struct calculatoranswer {
var nsexpress2:String = String(result)
}
var z = calculatoranswer()
Any ideas why this calculator will not calculate an expression twice will be much appreciated! Thanks.
All I had to do was put all of my calculating code in a function and call it in my view controller. Everything in my calculating swift file was put in a function, which I put inside a struct, and I called the function. Everything worked as planned, and I had my calculator. Structs and functions, not too hard even for beginners like me.