I am having a hard time displaying certain text in a ForEach loop in SwiftUI.
I have an array that I iterate over with ForEach. The problem is, I need to have certain text displayed for whether or not a specific date is selected in a calendar.
For example:
ForEach(reminders) { reminder in
if(Calendar.current.isDate(reminder.beginDate, equalTo: selectedPassedDate) {
HStack {
Text("The Text for the specific day")
} else {
Text("The text for the unspecific day")
}
}
}
When the array has more than 2 elements, the text for the non-matching day is being displayed multiple times (since it is inside the foreach). This makes sense, but I am trying to figure out a way to only display ONE of the "unspecified day" text instead of many.
I have tried adding a simple bool to display the other text, but I cannot change the state of it without a button or something.. I need to do this totally programmatically. I've also moved the else statement outside of the loop but again the secondary text is being displayed in the current day selection.
What can I do to solve this issue? If I can programmatically set the bool while conforming to SwiftUIs view then I can solve this, but I'm not sure how.
you could try something like this:
class FirstTime: ObservableObject {
var value = true
}
struct MyView: View {
...
#State var firstTime = FirstTime()
var body: some View {
VStack {
ForEach(reminders) { reminder in
HStack {
if (Calendar.current.isDate(reminder.beginDate, equalTo: selectedPassedDate) {
Text("The Text for the specific day")
} else {
if firstTime.value {
showTextOnce()
}
}
}
}
}
}
func showTextOnce() -> some View {
firstTime.value = false
return Text("The text for the unspecific day")
}
}
Related
I gather that the #Binding wrapper is used when a parent view and a child view both need to share a variable without violating the principle of having “a single source of truth”. This isn’t working as I expect when the variable is an Array. Essentially, the data that the child view assigns to this bound variable gets dropped so that the parent view sees an empty array.
The attached example is taken from the Developer Documentation (under XCode’s Help menu) for the #Binding wrapper. Apple’s code shows a simple video controller. It allows a user to play or pause videos (videos are stored as Episode structs). The child view is named PlayButton and toggles between a right-arrow (start) and a double-bar (stop). In Apple’s code the child view had a single variable wrapped with #Binding, which was the isPlaying Boolean variable. When the play button is tapped it toggles the isPlaying variable. As you might expect, the orginal code worked fine.
To demonstrate the problem I’ve modified the child view so that now accepts an array of Episodes. Please assume that the child view must report the size of the show titles to the parent view without violating the single source of truth principle. As a consequence of this need, there is also a new #Binding-wrapped array that records the number-of-characters in a show’s title (for each of the shows).
Additionally, the child view now shows Text Views reporting the show title of each Episode and the title’s size, just to prove that the code is being executed. The parent View should then be able to display the number of elements in its #State array, sizeOfShowTitles. As coded here, I expect the number of episodes to be 1. Instead, the parent view is reporting that sizeOfShowTitles has zero elements.
The playground contains just these 4 elements:
Episode (a struct that identifies the videos)
PlayButton (the child View)
PlayerView (the parent View)
the PlaygroundPage.current.setLiveView(PlayerView()) command, used to excuted SwiftUI in playgrounds.
Can anyone comment on why assignment of values to the bound array is failing?
Note 1:
The problem does not seem to lie with the .append function used in the child view. If you replace the append function with a simple assignment then the outcome is the same - there are no elements in the array in the parent View (tested, but not shown here).
Note 2.
As shown in Apples code (and retained here), assigning a value to a Boolean Type works as expected. So, it appears that the problem has something to do with the Array Type.
Note 3.
I’m using Swift 5.5 in XCode 5.4.1.
// Playground used to show the issue with saving a
// value to an array that is wrapped with #Binding
import SwiftUI
import PlaygroundSupport
// HELPER Struct: records metadata on shows that can be played in PlayerView
struct Episode: Identifiable {
var id = UUID()
var title = "Title"
var showTitle = "Show Title"
}
// CHILD View
// String Array)
struct PlayButton: View {
// input API
#Binding var isPlaying: Bool
var episodes: [ Episode ]
// output API
#Binding var charactersInShowTitle: [ Int ]
var body: some View {
Button(action: {
self.isPlaying.toggle()
}) {
Image(systemName: isPlaying ? "pause.circle" : "play.circle")
}
ForEach( episodes ) { episode in
Text( "CHILD: \( analyzeShowTitle( episode) )" )
}
}
func analyzeShowTitle( _ episode: Episode ) -> String {
let characterCount = episode.showTitle.count
charactersInShowTitle.append( characterCount )
return "\( episode.showTitle ) - \( characterCount ) chars"
}
}
// PARENT
// (modified to show the list of sizes from a String Array)
struct PlayerView: View {
let episodes = [ Episode(title: "Title 1",
showTitle: "Show 1"),
Episode( title: "Title 1",
showTitle: "Show 21")
]
#State private var isPlaying: Bool = false
#State private var sizeOfShowTitles = [ Int ]()
var body: some View {
VStack {
Text("Player App")
.foregroundStyle(isPlaying ? .primary : .secondary)
Text("")
PlayButton(isPlaying: $isPlaying,
episodes: episodes,
charactersInShowTitle: $sizeOfShowTitles )
Text("")
Text( "PARENT no. elements: \( $sizeOfShowTitles.wrappedValue.count)"
)
}
.frame(width: 300)
}
}
PlaygroundPage.current.setLiveView(PlayerView())
How about trying something like this example code,
using .onAppear{...} for updating each episode with the showTitle.count.
Also removing this "appending" from analyzeShowTitle function. This append(...) should not be used within the ForEach loop, because it triggers a view refresh,
which then triggers another append(...) etc...
struct PlayButton: View {
// input API
#Binding var isPlaying: Bool
var episodes: [Episode]
// output API
#Binding var charactersInShowTitle: [Int]
var body: some View {
Button(action: {
isPlaying.toggle()
}) {
Image(systemName: isPlaying ? "pause.circle" : "play.circle")
}
ForEach(episodes) { episode in
Text("CHILD: \(analyzeShowTitle(episode))")
}
.onAppear { // <-- here
for episode in episodes {
charactersInShowTitle.append(episode.showTitle.count)
}
}
}
func analyzeShowTitle( _ episode: Episode ) -> String {
return "\( episode.showTitle ) - \( episode.showTitle.count ) chars" // <-- here
}
}
I'm trying to figure out a way to have an Excel-like behavior with the Grids on Ext JS.
Here is the sample grid I am working with. So far we can already naviguate through the cells with the arrows but only in edit mode.
However what I am trying to reach is the naviguation with the arrows, TAB and Enter keys outside of the edit mode, just like excel.
I tried to integrate this piece of code which overrides the Editor class, hoping that it would change the behavior of the cells but it doesn't change a thing.
I believe this is the most important part that overrides the Editor class and tries to include the keys input :
Ext.override(Ext.Editor, {
startEdit: function (el, value) {
var me = this,
field = me.field;
me.completeEdit();
me.boundEl = Ext.get(el);
value = Ext.isDefined(value) ? value : me.boundEl.dom.innerHTML;
if (!me.rendered) {
me.render(me.parentEl || document.body);
}
if (me.fireEvent('beforestartedit', me, me.boundEl, value) !== false) {
me.startValue = value;
me.show();
field.reset();
if (deleteGridCellValue) {
field.setValue('');
me.editing = true;
me.completeEdit();
deleteGridCellValue = false; // reset global variable
}
else {
if (newGridCellValue == '') {
// default behaviour of Ext.Editor (see source if needed)
field.setValue(value);
}
else {
// custom behaviour to handle an alphanumeric key press from non-edit mode
field.setRawValue(newGridCellValue);
newGridCellValue = ''; // reset global variable
if (field instanceof Ext.form.field.ComboBox) {
// force the combo box's filtered dropdown list to be displayed (some browsers need this)
field.doQueryTask.delay(field.queryDelay);
}
}
me.realign(true);
field.focus(false, 10);
if (field.autoSize) {
field.autoSize();
}
me.editing = true;
}
}
}
});
This is the first time that I am working on a project that is outside of Comp-Sci classes so any help would be very much appreciated. Thanks !
i am learning angular. so i am not good in angular. i am showing data in tabular format with the help of ng-repeat. i have one dropdown and textbox for filter data showing by ng-repeat. fields name are populated in dropdown. so user will select field name and put corresponding value in textbox and search will perform accordingly and data will be shown.
my code is working partially. basically some kind of problem is there in SearchList function. the problem is when trying to search by id then SearchList is not working properly. so looking for help. what to fix in the code. my js fiddle https://jsfiddle.net/tridip/rnoo3bqc/6/
$scope.SearchList = function(row) {
if ($scope.selectedFieldName && $scope.searchText) {
var propVal = row[$scope.selectedFieldName.toLowerCase()];
if (propVal) {
return propVal.toUpperCase().indexOf($scope.searchText.toUpperCase()) > -1;
} else {
return false;
}
}
return true;
};
working version url
https://jsfiddle.net/tridip/rnoo3bqc/8/
You need to convert the id's from number to string, e.g. by concatenating an empty string:
var propVal = row[$scope.selectedFieldName.toLowerCase()] + '';
the problem was with id that's a numeric field and hence toUpperCase() was failing for it.
if (propVal) {
propVal.toString().toUpperCase().indexOf($scope.searchText.toUpperCase()) > -1;
} else {
return false;
}
I would like to change selected records display order in a combobox. The reason is that, as you can see from the screen shot, the combobox list so long therefore employee can't see selected values easily. Are there any method/function to change displayed value sort order based on the selected record index?
You can sort the store with a custom function. That will be reflected in the combo box.
That would be something like:
combo.getStore().sort([{
sorterFn: function(a, b) {
if (a.get('selected')) {
if (b.get('selected')) {
return a.get('name').localeCompare(b.get('name'));
} else {
return -1;
}
} else if (b.get('selected')) {
return 1;
} else {
return a.get('name').localeCompare(b.get('name'));
}
}
}]);
I'm trying to update the content of a combo box (using Griffon 1.2.0, with the JavaFX plugin).
My model:
class MyModel {
List monthList = FXCollections.observableList([new DateMidnight()])
def convertDate = [
fromString: { String s ->
return new DateMidnight(DateTimeFormat.forPattern("yyyy-MM").parseDateTime(s))
},
toString: { DateMidnight d ->
return "2011-10"
}
] as StringConverter
}
My view contains:
comboBox(items: (model.monthList), converter: model.convertDate)
Now I have a controller action which gets invoked when they push a button:
def load = {
execInsideUIAsync {
def months = myService.buildMonthList()
model.monthList.addAll(months)
}
}
The problem is that the combo box content never changes. Can anyone help me understand what I'm missing?
There's no documentation on ComboBox yet http://groovyfx.org/docs/guide/single.html#choiceBoxComboBox
Also, have I implemented the converter correctly?
The problem is that GroovyFX.comboBox creates a new List instead of using the one you pass as argument for items: This problem occurs with tableView as well. A temporary workaround would be to set the items property directly, like this
comboBox(id: 'combo')
noparent { combo.items = model.monthList }