How implement many custom ListItems, like its implemented in standart blackberry calendar app.
The following screenshot shows what I mean
Especially I interested what is the second control with right arrow.
Thanks.
You can have multiple "types" of list items.
Attach your different types of listItemComponents each with a different type. e.g.
listItemComponents: [
ListItemComponent {
type: "itemA"
Container {
Label {
text: ListItemData.title
textStyle.color: Color.Blue
}
}
},
ListItemComponent {
type: "itemB"
Container {
Label {
text: ListItemData.title
textStyle.color: Color.Red
}
}
}
]
Then add this function to your listview (I'm using a property of "mytype" but you could check any property of the data model or even base it on the indexpath):
function itemType(data, indexPath) {
if (data.mytype == "typea") {
return "itemA";
} else {
return "itemB";
}
}
Now when you add your data to your datamodel make sure you specify "mytype" and the listview will automatically use the ListItemComponent for the relative type.
You can easily have different sized list items, different designs even have them work with different data structures.
Related
I am new to Xcode, and facing some issues while creating a table view.
Problem: In iOS 14 Plus simulator, only 1 columns is shown - "Category" and without header. The entire column "Total" is completely missing. However, when I switch the simulation to ipad, everything is working fine. Really appreciate if someone could help to resolve this issue.
struct OverallObj: Identifiable {
var category: String
var total: Int
var id: String {
category
}
static var summary: [OverallObj] {
[
OverallObj(category: "1st", total: 1000),
OverallObj(category: "2nd", total: 1000)
]
}
}
var body: some View {
Table(overallView) {
TableColumn("Category", value: \.category)
TableColumn("Total") { overallObj in
Text("\(overallObj.total)")
}
}
}
I am expecting the table to show 2 columns - "Category" and "Total" with headers
The problem in your specific case is caused by the compact horizontal size on iOS.
From the official documentation:
MacOS and iPadOS support SwiftUI tables. On iOS, and in other
situations with a compact horizontal size class, tables don’t show
headers and collapse all columns after the first. If you present a
table on iOS, you can customize the table’s appearance by implementing
compact-specific logic in the first column.
You can implement compact-specific logic in the first column of a table in SwiftUI by using the environment modifier and checking the sizeCategory environment value.If the size category is compact, you can return a compact version of the view for the first column with all the data you need to display, otherwise return the normal version of the view.
Here is an example:
#Environment(\.horizontalSizeClass) var sizeCategory
var body: some View {
Table(overallView) {
TableColumn("Category") { item in
if (sizeCategory == .compact) {
// Return compact version of the view
Text("Compact")
} else {
// Return normal version of the view
Text("Normal")
}
}
TableColumn("Total") { overallObj in
Text("\(overallObj.total)")
}
}
Using tables on different platforms
macOS and iPadOS support SwiftUI tables. On iOS, and in other situations with a compact horizontal size class, tables don’t show headers and collapse all columns after the first. If you present a table on iOS, you can customize the table’s appearance by implementing compact-specific logic in the first column.
Source: https://developer.apple.com/documentation/SwiftUI/Table
With WWDC 2022, Apple introduced the Transferable protocol to support Drag & Drop operations in an easy way. How can I use this new technique (in combination with the new draggable and dropDestination modifiers) for SwiftUI tables (not lists)?
The TableRowContent does not support the draggable and dropDestination modifiers. Also, when applying the modifiers directly to the views in the TableColumns, the drag / drop operation will only work on for that specific cell, and not the entire row. When adding the modifiers to all cells, it still does not work when dragging e.g. in an empty space inside of a row.
struct Item: Identifiable, Codable, Transferable {
let id = UUID()
let text: String
static var transferRepresentation: some TransferRepresentation {
CodableRepresentation(contentType: .text)
}
}
struct Test: View {
var body: some View {
Table {
TableColumn("Column 1") { item in
Text(item.text)
.draggable(item) // This does only work when dragging not in the space between two columns
}
TableColumn("Column 2") { item in
Text(item.text)
.draggable(item) // This does only work when dragging not in the space between two columns
}
} rows: {
ForEach([Item(text: "Hello"), Item(text: "World")]) { item in
TableRow(item)
.draggable(item) // This causes a compile-time error
.itemProvider { ... } // This does not work with Transferable and thus support my use case
}
}
}
}
I want a similar behavior as the itemProvider modifier and the recently added contextMenu modifier on the TableRowContent , which allow the respective operation on the whole table row. I cannot use itemProvider, since it requires to return an NSItemProvider, which does not support my use case of dragging a file from a network drive to the Mac hard drive.
I found out that you can register a Transferable object in the NSItemProvider:
TableRow(item)
.itemProvider {
let provider = NSItemProvider()
provider.register(item)
return provider
}
Unfortunately the itemProvider closure is already called when the dragging session starts instead of when the drop was performed. But I guess this problem is a different question to answer.
This is the next part of that question.
I've got the follow code.
The initial view of the app:
struct InitialView : View {
var body: some View {
Group {
PresentationButton(destination: ObjectsListView()) {
Text("Show ListView")
}
PresentationButton(destination: AnotherObjectsListView()) {
Text("Show AnotherListView")
}
}
}
}
The list view of the objects:
struct ObjectsListView : View {
#Environment(\.myObjectsStore.objects) var myObjectsStores: Places
var body: some View {
Group {
Section {
ForEach(myObjectsStore.objects) { object in
NavigationLink(destination: ObjectDetailView(object: object)) {
ObjectCell(object: object)
}
}
}
Section {
// this little boi
PresentationButton(destination: ObjectDetailView(objectToEdit: MyObject(store: myObjectsStore))) {
Text("Add New Object")
}
}
}
}
}
The detail view:
struct ObjectsDetailView : View {
#Binding var myObject: MyObject
var body: some View {
Text("\(myObject.title)")
}
}
So the problem is quite complex.
The ObjectsListView creates instance of the MyObject(store: myObjectsStore) on itself initialization while computing body.
The MyObject object is setting its store property on itself initialization, since it should know is it belongs to myObjectsStore or to anotherMyObjectsStore.
The myObjectsStore are #BindableObjects since their changes are managing by SwiftUI itself.
So this behavior ends up that I've unexpected MyObject() initializations since the Views are computing itself. Like:
First MyObject creates on the ObjectsListView initialization.
Second MyObject creates on its PresentationButton pressing (the expected one).
Third (any sometimes comes even fourth) MyObject creates on dismissing ObjectsDetailView.
So I can't figure what pattern should I use this case to create only one object?
The only thing that I'd come to is to make the follow code:
struct ObjectsListView : View {
#Environment(\.myObjectsStore.objects) var myObjectsStores: Places
#State var buttonPressed = false
var body: some View {
Group {
if buttonPressed {
ObjectDetailView(objectToEdit: MyObject(store: myObjectsStore))
} else {
Section {
ForEach(myObjectsStore.objects) { object in
NavigationLink(destination: ObjectDetailView(object: object)) {
ObjectCell(object: object)
}
}
}
Section {
Button(action: {
self.buttonPressed.toggle()
}) {
Text("Add New Object")
}
}
}
}
}
}
Which simply redraw ObjectsListView to detail view conditionally. But it's completely out of iOS guidelines. So how to create the Only One object for another view in SwiftUI?
UPD:
Here's the project that represents the bug with Object duplication.
I'm still have no idea why the objects are duplicating in this case. But at least I know the reason yet. And the reason is this line:
#Environment(\.myObjectsStore.objects) var myObjectsStores: Places
I've tried to share my model with this wrapper to make it available in every single view (including Modal one) without passing them as an arg to the new view initializer, which are unavailable by the other ways, like #EnvironmentObject wrapper. And for some reason #Environment(\.keyPath) wrapper makes duplications.
So I'd simply replace all variables from Environment(\.) to ObjectBinding and now everything works well.
I've found the solution to this.
Here's the project repo that represents the bug with Object duplication and the version that fix this. I'm still have no idea how objects have been duplicate in that case. But I figured out why. It happens because this line:
#Environment(\.myObjectsStore.objects) var myObjectsStores: MyObjectsStore
I've used #Environment(\.key) to connect my model to each view in the navigation stack including Modal one, which are unavailable by the other ways provided in SwiftUI, e.g.: #State, #ObjectBinding, #EnvironmentObject. And for some reason #Environment(\.key) wrapper produce these duplications.
So I'd simply replace all variables from #Environment(\.) to #ObjectBinding and now almost everything works well.
Note: The code is in the rep is still creates one additional object by each workflow walkthrough. So it creates two objects totally instead of one. This behavior could be fixed by way provided in this answer.
I'm developing a BlackBerry 10 mobile application using the Momentics IDE (native SDK).
I have a simple ListView and I want to change the background of a specific item when clicked according to the selection status. I thought th code below will work but it doesn't, any one can help one this ?
ListView{
id: simpleList
dataModel: groupDataModel
listItemComponents: [
ListItemComponent {
type: "item"
SQLvListItem {
id: containerItem
itemLabel: ListItemData.label
// Item Label Style
itemFontSize: ListItemData.fontSize
itemFontName: ListItemData.fontName
itemFontStyle: ListItemData.fontStyle
itemFontColor: containerItem.ListItem.selected ? ListItemData.fontColorSelected : ListItemData.fontColor
//Item background
containerBorderColor: ListItemData.borderColor
containerBackgroundColor: containerItem.ListItem.selected ? ListItemData.backgroundColorSelected : ListItemData.backgroundColor
}
}
]
onTriggered: {
var selectedItem = dataModel.data(indexPath);
if (selected() == indexPath){
select(indexPath, false);
dataModel.itemUpdated(indexPath)
}
else{
select(indexPath, true);
}
}
}
PS: the "SQLvListItem" is a custom item which I created
In your SQLvListItem, add a property that is a bool for "IsSelected" and add containerSelectedBackgroundColor with containerUnselectedBackgroundColor and itemSelectedFontColor with itemUnselectedFontColor.
Then you can make a function like so:
function selectedBackground(selectedItem){
if(selectedItem.isSelected){
selectedItem.itemFontColor: selectedItem.itemSelectedFontColor;
selectedItem.containerBackgroundColor: selectedItem.itemSelectedFontColor;
}
else{
selectedItem.itemFontColor: selectedItem.itemUnselectedFontColor;
selectedItem.containerBackgroundColor: selectedItem.itemUnselectedFontColor;
}
}
That way you have variables to store the selectedColor and unselectedColor for both font and background, and then you can swap them out on the fly. When it gets selected, just flag the isSelected as true when it is selected, and false when it isn't.
I only have a snippet of your code to go off of, so I don't know for sure if this will work, but it's worth a shot!
Actually it was so simple, you just need to use toggleSelection() function which toggles selection on an item ; if the item is selected, it becomes deselected and if the item is deselected, it becomes selected.
ListView{
id: simpleList
dataModel: groupDataModel
listItemComponents: [
ListItemComponent {
type: "item"
SQLvListItem {
id: containerItem
itemLabel: ListItemData.label
// Item Label Style
itemFontSize: ListItemData.fontSize
itemFontName: ListItemData.fontName
itemFontStyle: ListItemData.fontStyle
itemFontColor: containerItem.ListItem.selected ? ListItemData.fontColorSelected : ListItemData.fontColor
//Item background
containerBorderColor: ListItemData.borderColor
containerBackgroundColor: containerItem.ListItem.selected ? ListItemData.backgroundColorSelected : ListItemData.backgroundColor
}
}
]
onTriggered: {
var selectedItem = dataModel.data(indexPath);
toggleSelection(indexPath) // (solution 1)
// Or you can use also
select(indexPath, (!isSelected(indexPath))) // (solution 2)
}
}
I have a page, which is used for building queries and running them against different entities (Kind of a query builder/generic search).
The results are displayed in JQGrid, so effectively the same grid will be used for rendering results from different entities.
This results grid has to support context menus, which will differ for each entity. So I need a way to change the context menu as per the entity. Each entity may have different number of menu items in context menu and each item may respond in a different manner (sometimes an alert, sometimes an action spawning in a different tab).
Rendering different menus (through li) is not an issue but attaching the methods to the li is proving to be a challenge. Any pointers will be highly appreciated.
I am using jquery.contextmenu-ui.js .
Following is from a sample that I picked from their (JQGrid) site
function initGrid() {
$("#EntityGrid").contextMenu('cMenu'
,{
bindings: { /* I would like to avoid this and pass all the actions to one method*/
'edit': function (t) {
editRow();
},
'add': function (t) {
addRow();
},
'del': function (t) {
delRow();
}
},
onContextMenu: function (event, menu) {
var rowId = $(event.target).parent("tr").attr("id")
var grid = $("#EntityGrid");
grid.setSelection(rowId);
return true;
}
}
);
}
Thanks,
Avinash
You can use onShowMenu callback of contextMenu instead of static binding using bindings. In the same way the menuId used as the first parameter of contextMenu could be the id of dynamically created div with empty <ul>. The onShowMenu has the form
onShowMenu: function (e, $menu) {
// here one can clear `<ul>` child of $menu
// and append it with "<li>" items
return $menu;
}
In the answer you will find an example of the code which build menu dynamically.