QML Distinctive TableViewColumn Delegate - delegates

I have a TableView object which consists of four TableViewColumn objects.
Two of the TableViewColumn objects' delegates are ComboBox.
I want the second delegate to change its model depending on the user's choice in the first delegate. I have managed this.
However, this results in changing the second delegate's model in all of the other rows of the table. I want this change to be applied only in the respective row so as to be able to have different models in each row.
Is this possible to happen? And if so, how?
TableViewColumn{
id: usageCol
title: "Type"
property string modelName: "Power Out"
delegate: ComboBox {
id: usageCombo
model:
ListModel{
id: usageModel
ListElement { text: "PowerOut" }
ListElement { text: "AnalogOut" }
ListElement { text: "PwmOut" }
ListElement { text: "DigitalOut" }
ListElement { text: "BldcOut" }
ListElement { text: "AnalogIn" }
ListElement { text: "PwmIn" }
ListElement { text: "DigitalIn" }
ListElement { text: "Can" }
}
currentIndex: 0
height: 16
anchors.fill: parent
onCurrentTextChanged: {
tableModel.setProperty(styleData.row,"use",currentText);
usageCol.modelName = currentText;
}
}
width: tableConfig.width/tableConfig.columnCount
}
TableViewColumn{
id: pinCol
title: "PIN"
property string modelName: usageCol.modelName
delegate: ComboBox {
id: pinCombo
model: {if (pinCol.modelName === "PowerOut") { modelPowerOut;}
else if (pinCol.modelName === "AnalogOut") { modelAnalogOut;}
else if (pinCol.modelName === "AnalogIn") { modelAnalogIn;}
else if (pinCol.modelName === "PwmOut") { modelPwmOut; }
else if (pinCol.modelName === "DigitalOut" || pinCol.modelName === "BldcOut" || pinCol.modelName === "DigitalIn") { modelDigitalBldc; }
else if (pinCol.modelName === "PwmIn") { modelPwmIn; }
else if (pinCol.modelName === "Can") { modelCan; }
}
/* Try Loader for delegate changing the model as needed. */
height: 16
anchors.fill: parent
onCurrentTextChanged: {
tableModel.setProperty(styleData.row,"pin",currentText);
}
}
width: tableConfig.width/tableConfig.columnCount
}
[SOLVED]
TableViewColumn{
id: pinCol
title: "PIN"
delegate: ComboBox {
id: pinCombo
model:
{
switch(tableModel.get(styleData.row).use){
case "PowerOut": return modelPowerOut
case "AnalogOut": return modelAnalogOut
case "AnalogIn": return modelAnalogIn
case "PwmOut": return modelPwmOut
case "DigitalOut" || "BldcOut" || "DigitalIn": return modelDigitalBldc
case "PwmIn": return modelPwmIn
case "Can": return modelCan
}
}
height: 16
anchors.fill: parent
onCurrentTextChanged: {
tableModel.setProperty(styleData.row,"pin",currentText);
}
}
width: tableConfig.width/tableConfig.columnCount
}

You can not use modelName as it is a common property for the whole column. You should rely on the model from the main TableView instead (corresponding to a row).
Removing all modelName properties and using tableModel.get(styleData.row).use instead of pinCol.modelName should fix your problem.

Related

SwipeAction does not work in TabView SwiftUI

I have List in the TabView. List has text/button with delete and update swipe action. The swipe action does not work in the tabview but it is work fine without tab view. Below is my code.
TabView {
List {
ForEach(0..<5, id: \.self) {index in
Text(“\(index)”)
.swipeActions(edge: .trailing, allowsFullSwipe: false) {
Button {
} label: {
Label {
Text("Delete")
} icon: {
Image("ic_delete")
.renderingMode(.template)
.foregroundColor(.white)
}
}
.tint(.red)
Button {
} label: {
Label {
Text("Update")
} icon: {
Image("ic_edit")
.renderingMode(.template)
.foregroundColor(.white)
}
}
.tint(.accentColor)
}
}
}
}
.tabViewStyle(.page(indexDisplayMode: .never))
Can Anyone help me to fix this issue?

Binding in SwiftUI fails to work inside of a loop

For my understanding, I've written the following code which expands / collapses a section inside of a list.
struct WORKING_CollapsableListView: View {
#State var sectionExpansionStates = [true, true, true]
var body: some View {
VStack {
List {
Section(header: CollapsableSectionHeader(expansionState: self.$sectionExpansionStates[0])) {
if self.sectionExpansionStates[0] {
ForEach(0..<10) { item in
Text("\(item) is \(self.sectionExpansionStates[0] ? "Expanded" : "Collapsed")")
.frame(height: self.sectionExpansionStates[0] ? 10 : 10)
}
}
}
Section(header: CollapsableSectionHeader(expansionState: self.$sectionExpansionStates[1])) {
if self.sectionExpansionStates[1] {
ForEach(0..<10) { item in
Text("\(item) is \(self.sectionExpansionStates[1] ? "Expanded" : "Collapsed")")
.frame(height: self.sectionExpansionStates[1] ? 10 : 10)
}
}
}
Section(header: CollapsableSectionHeader(expansionState: self.$sectionExpansionStates[2])) {
if self.sectionExpansionStates[2] {
ForEach(0..<10) { item in
Text("\(item) is \(self.sectionExpansionStates[2] ? "Expanded" : "Collapsed")")
.frame(height: self.sectionExpansionStates[2] ? 10 : 10)
}
}
}
}
}
}
}
struct CollapsableSectionHeader: View {
#Binding var expansionState: Bool
var body: some View {
Button(action: {
self.expansionState.toggle()
}) {
Text("HEADER: \(expansionState ? "Expanded" : "Collapsed")")
.bold()
}
}
}
This works as expected. However the following code does NOT work. All I've done is replaced the multiple sections with a ForEach. This code should be identical in its behavior, but nothing happens when I tap on the section headers. What am I missing? It's as though the binding isn't working.
struct NOT_WORKING_CollapsableListView: View {
#State var sectionExpansionStates = [true, true, true]
var body: some View {
VStack {
List {
ForEach(0 ..< 3) { section in
Section(header: CollapsableSectionHeader(expansionState: self.$sectionExpansionStates[section])) {
if self.sectionExpansionStates[section] {
ForEach(0..<10) { item in
Text("\(item) is \(self.sectionExpansionStates[section] ? "Expanded" : "Collapsed")")
.frame(height: self.sectionExpansionStates[section] ? 10 : 10)
}
}
}
}
}
}
}
}
It is due to statically_ranged_ForEach... as I experienced here on SO it is most confused concept in SwiftUI.. anyway - the solution is to use dynamic container of explicit models for sections.
Here is simplified working demo of your code (but the idea should be easily adoptable to your not provided components).
Tested with Xcode 11.4 / iOS 13.4
// simple demo model for sections
struct SectionModel: Identifiable {
let id: Int
var expanded = true
}
struct TestCollapsableListView: View {
// dynamic container with model, state is triggered
#State var sections = [SectionModel(id: 0), SectionModel(id: 1), SectionModel(id: 2)]
var body: some View {
VStack {
List {
ForEach(sections) { section in
Section(header: Button("Section \(section.id)") { self.sections[section.id].expanded.toggle() }) {
if section.expanded {
ForEach(0..<10) { item in
Text("\(item) is \(section.expanded ? "Expanded" : "Collapsed")")
.frame(height: section.expanded ? 10 : 10)
}
}
}
}
}
}
}
}

swiftUi : 2 Pickers on one screen - app crash with "Index out of range"

When I try to put 2 pikers with a different number of rows on-screen with different observers.
if I select in one number of the row that not exists in the second,
when I move to the second picker app crash with this message:
"Fatal error: Index out of range"
public enum kTrackType {
case audio
case text
}
class kTrack: NSObject, Identifiable {
public var id = UUID()
public var trakcId: String
public var title: String
public var type: kTrackType
public init(id: String, title: String, type: kTrackType) {
self.trakcId = id
self.title = title
self.type = type
}
}
and this is the main struct:
struct SelectedAudioAndSubtileView: View {
let geometry: GeometryProxy
#State var subtitlesList = [kTrack(id: "t0", title: "None", type: kTrackType.text),
kTrack(id: "t1", title: "En", type: kTrackType.text),
kTrack(id: "t2", title: "Rus", type: kTrackType.text),
kTrack(id: "t3", title: "Spn", type: kTrackType.text)]
#State var multiAudioList = [kTrack(id: "s0", title: "En", type: kTrackType.audio),
kTrack(id: "s1", title: "Rus", type: kTrackType.audio)]
#Binding var showSubtitlesPicker: Bool
#State private var selectedAudioPicker: Int = 0
#State private var selectedSubtitlePicker: Int = 0
#State private var selectedType = 0
var body: some View {
VStack {
Picker(selection: $selectedType, label: EmptyView()) {
Text("Audio").tag(0)
Text("Subtitle").tag(1)
}
.pickerStyle(SegmentedPickerStyle())
Text(self.selectedType == 0 ? "Select Audio" : "Select Subtitle")
Divider()
if selectedType == 0 {
Picker(selection: self.$selectedAudioPicker, label: Text("")) {
ForEach(self.multiAudioList, id: \.id){ name in
Text(name.title)
}
}
} else {
Picker(selection: self.$selectedSubtitlePicker, label: Text("")) {
ForEach(self.subtitlesList, id: \.id){ name in
Text(name.title)
}
}
}
Divider()
}
.background(Color(#colorLiteral(red: 0.9686274529, green: 0.78039217, blue: 0.3450980484, alpha: 1)))
.offset(y: geometry.size.height - 330)
}
After recheck, the crash happened also if you have same rows in 2 pickers!
Here is the situation :
a) the selectedValue should match the tag value, therefore in ForEach, it's better to use index not the id so that you can add tag for each items.
b) the ForEach structure is a complex one and usually to be reused for performance. So in order to force it refresh, id() modifier can be added to extra ForEach structures. There must be one ForEach without id which provides the real underlying data layer.
if selectedType == 0 {
Picker (selection: self.$selectedAudioPicker, label: Text("")) {
ForEach(0..<self.multiAudioList.count){ index in
Text(self.multiAudioList[index].title).tag(index)
}
}
} else if selectedType == 1 {
Picker(selection: self.$selectedSubtitlePicker, label: Text("")) {
ForEach(0..<self.subtitlesList.count){ index in
Text(self.subtitlesList[index].title).tag(index)
}.id(0)
}
}

Rally sorting by Parent

What I Need
I would like to sort my grid/store by the Parent field, but because the Parent field that is fetched is an object, it fails to fetch any records when I put a sorter based on the Parent property. Even if I add a sorter function, it is not called. I am using a rallygrid, not sure if that makes a difference
sorters: [{
property: 'Parent',
direction: 'DESC',
sorterFn: function(one, two) {
console.log('one',one);
console.log('two',two); // console never shows these
return -1;
}
}]
What I have tried
To get around displaying the object, I have added a renderer function to the Parent column. I tried adding a doSort to the column, and that function is called, but sorting the store does not call my sorterFn, it only uses the property and direction (similar to the console.log() that fails to run above)
Here is an example of a custom App that works properly. The key is setting the default storeConfig of remoteSort to false!
Ext.define('CustomApp', {
extend: 'Rally.app.App',
componentCls: 'app',
launch: function() {
App = this;
Rally.data.ModelFactory.getModel({
type: 'PortfolioItem/Feature',
success: function(model) {
App.add({
xtype: 'rallygrid',
id : 'grid',
model: model,
columnCfgs: [
'FormattedID',
'Name',
{dataIndex: 'Parent', name: 'Parent',
doSort: function(state) {
var ds = this.up('grid').getStore();
var field = this.getSortParam();
console.log('field',field);
ds.sort({
property: field,
direction: state,
sorterFn: function(v1, v2){
v1 = v1.get(field);
v2 = v2.get(field);
console.log('v1',v1);
console.log('v2',v2);
if (!v1 && !v2) {
return 0;
} else if (!v2) {
return 1;
} else if (!v1) {
return -1;
}
return v1.Name.localeCompare(v2.Name);
}
});
},
renderer: function(value, meta, record) {
var ret = record.raw.Parent;
if (ret) {
return ret.Name;
} else {
return record.data.Name;
}
}
}
],
storeConfig: {
remoteSort: false
}
});
}
});
}
});

How to set a image source outside Component

I have a image showing up in a Dialog in my QML app, and I want to be able to change that image later on using onClicked, which I pass by a function to check if the variable I want in the new source URL is one of them I want.
I've tried just by using Image.source = "NEWURL" which is a no go. Also the id of the component the image is in, and the dialog like: id.source = "neurl" - no go.
How do I do that?
EDIT: Added more code; both the function and then listitm used to click. The image is a web image, and I want to have the conncectedUser value (which is a user name) inside the url.
Here is all the related code:
// Check if users is really a user, and if; show skin
function checkCurrentUser(currentUser) {
console.debug('Debug: Check user "'+currentUser+'" if actually a user.')
if (currentUser == "Ingen online") {
currentUser = "Notch" // reset currentUser if pushed earlier
console.debug('Debug: It was not a real user. Showing '+currentUser+' instead')
Image.source = "http://blabla"+currentUser+"yesyes"
}
else {
console.debug('Debug: It was a real user.')
Image.source = "http://blabla"+currentUser+"yesyes"
}
return "http://blabla"+currentUser+"yesyes""
}
// the dialog I want to show with a image
Component {
id: userDialog
Dialog {
id: dialogueUser
title: i18n.tr("Image")
Image {
id: usersSkin
fillMode: Image.PreserveAspectFit
source: "URL"
sourceSize.height: 1200
}
Button {
text: i18n.tr("Close")
color: "red"
onClicked: PopupUtils.close(dialogueUser)
}
}
}
// and then the list containting each link, which on click should show the user image
ListView {
id: userList
width: parent.width
height: units.gu(5)
model: msmData
delegate: ListItem.Standard {
text: connectedUser
onClicked: {
console.debug('Debug: User clicked "'+connectedUser+'"')
checkCurrentUser(connectedUser)
PopupUtils.open(userDialog, userList)
}
}
header: ListItem.Header { text: i18n.tr("Connected Users") }
section.property: "type"
section.criteria: ViewSection.FullString
section.delegate: ListItem.Header { text: i18n.tr(section) }
}
I am not sure if I understood your question correctly, but I will give it a try:
Component
{
id: userDialog
Dialog
{
property int sourceState : 1
id: dialogueUser
title: i18n.tr("Image")
Image
{
id: usersSkin
fillMode: Image.PreserveAspectFit
source: 1 == sourceState ? "OLDURL" : "NEWURL"
sourceSize.height: 1200
}
Button
{
text: i18n.tr("Close")
color: "red"
onClicked:
{
PopupUtils.close(dialogueUser)
dialogueUser.sourceState = 0
}
}
}
}
What I finally did was to just reset the variable in the image URL, and then show the dialog. Working now.

Resources