Swift table only show 1 column - uitableview

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

Related

SwiftUI NavigationSplitView column visibility on iPhone?

I'm trying to create a 3-column layout in SwiftUI.
The first column is a LazyVGrid of selectable items. The selection then impacts a list of items in a content view (second column) which also isn't a SwiftUI list but a VStack + other views in a scrollview. Selecting items on that column impacts the detail view.
I got it all to work on the iPad, but this is because the iPad displays multiple columns at a time and NavigationSplitView supports gestures on the iPad as well as column visibility settings in code.
The problem is that I can't find a way to programmatically navigate from one column to another on the iPhone as it doesn't seem to respond to column visibility bindings.
I initially had it working with navigation links for each item on my grid where the destination was set to a view, but the code smelled pretty bad.
Eventually, I came up with the code below. In the first column I have my grid view which has a custom onSelect modifier that I trigger whenever an item is selected. That's where I'm trying to change column visibility. I tried setting vm.navigationColumnVisibility = .detailOnly, but the iPhone seems to ignore it.
I was able to get it to work exactly as expected by changing the grid to a List. The selection property/Binding in the List view seemed to trigger navigation on the iPhone. However, that's not the desired UI/UX.
Any advice on how to trigger navigation between columns programatically on the iPhone or a better way to adapt this code to achieve the described UI/UX?
struct JNavSplitView: View {
#EnvironmentObject var vm: JNavSplitViewModel
#State private var book: Book? = nil
#State private var entry: Entry? = nil
var sheet: some View {
#if os(macOS)
JEntryCreateFormMacOS(book: book)
#else
JEntryCreateForm(book: book)
.onCreate { entry in
self.entry = entry
}
#endif
}
var body: some View {
NavigationSplitView(columnVisibility: $vm.navigationColumnVisibility) {
BooksGridView(selected: $book)
.onSelect { book in
self.entry = book.getEntries(forDate: Date()).first
}
} content: {
BookEntriesView(book: book, selected: $entry)
} detail: {
PageCollectionView(entry: $entry)
}
.navigationViewStyle(DoubleColumnNavigationViewStyle())
#if os(macOS)
.toolbarBackground(Color("Purple 1000"), for: .windowToolbar)
#endif
.environmentObject(vm)
.sheet(isPresented: $vm.presentNewEntryForm, content: {
sheet
})
}
}

How to use Transferable for Table Rows in SwiftUI

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.

Reusability support in List in SwiftUI

I'm currently working on a project that uses SwiftUI. I was trying to use List to display a list of let's say 10 items.
Does List supports reusability just like the UITableview?
I went through multiple posts and all of them says that List supports reusability: Does the List in SwiftUI reuse cells similar to UITableView?
But the memory map of the project says something else. All the Views in the List are created at once and not reused.
Edit
Here is how I created the List:
List {
Section(header: TableHeader()) {
ForEach(0..<10) {_ in
TableRow()
}
}
}
TableHeader and TableRow are the custom views created.
List actually provides same technique as UITableView for reusable identifier. Your code make it like a scroll view.
The proper way to do is providing items as iterating data.
struct Item: Identifiable {
var id = UUID().uuidString
var name: String
}
#State private var items = (1...1000).map { Item(name: "Item \($0)") }
...
List(items) {
Text($0.name)
}
View hierarchy debugger shows only 17 rows in memory

What is the generic type of HStack?

I'm building a calculator app in SwiftUI as part of an iOS development course. The UI is as follows:
As we can see, there five rows of buttons, each button has a text, as well as the result panel is a text. Thus, extracting the text, buttons and rows into separate views works perfectly. Following is the code that creates a row.
struct RowView: View {
let buttons: [CalculatorButton]
#Binding var result: String
let calc: Calculator
var body: some View {
createCells(self.buttons)
}
private func createCells(_ buttons: [CalculatorButton]) -> some View {
if buttons.count % 2 == 0 {
return AnyView(HStack(spacing: 1) {
ForEach(buttons, id: \.label) { button in
ButtonView(button: button, result: self.$result, calc: self.calc)
}
})
} else {
return AnyView(HStack(spacing: 1) {
ButtonView(button: buttons[0], result: self.$result, calc: self.calc)
createCells(buttons.suffix(2))
})
}
}
}
The idea is that if there are even number of buttons in a row, they are simply stacked horizontally. For the last row, we manually place the first button, and then make a recursive call that places the remaining two buttons horizontally. Sweet!
However, the AnyView(HStack(spacing: 1) code is repeated in the if-else block. I tried to create a local variable for the contents of the HStack, but couldn't find a type that successfully compiled for both if and else.
Is it possible to do so? I'm using Xcode 11.5, with Swift 5.

SwiftUI: NavigationLink pops immediately if used within ForEach

I'm using a NavigationLink inside of a ForEach in a List to build a basic list of buttons each leading to a separate detail screen.
When I tap on any of the list cells, it transitions to the detail view of that cell but then immediately pops back to the main menu screen.
Not using the ForEach helps to avoid this behavior, but not desired.
Here is the relevant code:
struct MainMenuView: View {
...
private let menuItems: [MainMenuItem] = [
MainMenuItem(type: .type1),
MainMenuItem(type: .type2),
MainMenuItem(type: .typeN),
]
var body: some View {
List {
ForEach(menuItems) { item in
NavigationLink(destination: self.destination(item.destination)) {
MainMenuCell(menuItem: item)
}
}
}
}
// Constructs destination views for the navigation link
private func destination(_ destination: ScreenDestination) -> AnyView {
switch destination {
case .type1:
return factory.makeType1Screen()
case .type2:
return factory.makeType2Screen()
case .typeN:
return factory.makeTypeNScreen()
}
}
If you have a #State, #Binding or #ObservedObject in MainMenuView, the body itself is regenerated (menuItems get computed again) which causes the NavigationLink to invalidate (actually the id change does that). So you must not modify the menuItems arrays id-s from the detail view.
If they are generated every time consider setting a constant id or store in a non modifying part, like in a viewmodel.
Maybe I found the reason of this bug...
if you use iOS 15 (not found iOS 14),
and you write the code NavigationLink to go to same View in different locations in your projects, then this bug appear.
So I simply made another View that has different destination View name but the same contents... then it works..
you can try....
sorry for my poor English...

Resources