The list in the view is limited to showing columns. I want to include the column that I want in the columns shown in the view.
How should I do?
#State var showMyRow = false
var body: some view {
Vstack {
Button(“bt”) {
self.showMyRow.toggle()
}
ImageView
....
List {
ForEach
.
.
. // if showMyRow is true, view show this row
}
}
}
Above image, showMyRow is false.
After button action, showMyRow is true.
If showMyRow is true, show me at the 7row
I'm guessing that you're trying to present multiple columns horizontally on the screen?
If so, then you can put the Lists inside a Stack
HStack {
List {
Text("Hello")
}
List {
Text("There")
}
}
And if there's a lot of columns, then you can wrap the HStack inside a scroll view.
var body: some View {
ScrollView(.horizontal) {
HStack {
List {
Text("column 1")
}
List {
Text("column 2")
}
List {
Text("column 3")
}
List {
Text("column 4")
}
}
.frame(width: 1000, height: 800)
.background(Color.red)
}
}
Updated Answer:
Not sure if I understood your question perfectly, but if you just want a list that you can add and delete things from, then the easiest would be to create a separate SwiftUI file holding a struct for the row and an ObservableObject class that checks for any changes.
1) SwiftUI File with ListItem struct & ObservableObject class:
import SwiftUI
struct ListItem: Identifiable {
let id = UUID()
let title: String
}
class Items: ObservableObject {
#Published var rows = [ListItem]()
}
2) ContentView with your image view and list:
This also has a removeItems method so you can swipe any row to delete it.
struct ContentView: View {
#ObservedObject var items = Items()
var body: some View {
NavigationView{
VStack{
Image("image")
.resizable()
.frame(width: 200, height: 200)
.padding(50)
List {
ForEach(items.rows) { item in
Text(item.title)
}
.onDelete(perform: removeItems)
}
}
.navigationBarTitle("My List")
.navigationBarItems(trailing: Button(action : {
let row = ListItem(title: "New Row")
self.items.rows.append(row)
}) {
Image(systemName: "plus")
}
)
}
}
func removeItems(at offsets: IndexSet) {
items.rows.remove(atOffsets: offsets)
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
Related
Since iOS 16 there is a new feature for the ".sheet" modifier called ".presentationDetents". ".presentationDetents" has a parameter called "selection" where you can pass a Binding. You can programmatically resize the sheet with the "selection" parameter. As soon as you change the sheet size for example from PresentationDetent.medium to PresentationDetent.large right after changed the page with a "NavigationLink" the View below gets cut off:
But if I slightly move (resize) the sheet afterwards the cut off below is going to disappear:
The view hierarchy is also strange:
If you add a delay by 0.6s for resizing the sheet, the cut off won't happen.
.onAppear {
DispatchQueue.main.asyncAfter(deadline: .now() + 0.6) {
currentSelection = .large
}
}
You can find the code below:
import SwiftUI
struct ContentView: View {
#State private var sheetIsOpened = false
#State private var currentSelection = PresentationDetent.medium
var body: some View {
Text("Click to open a sheet")
.padding()
.onTapGesture {
sheetIsOpened = true
}
.sheet(isPresented: $sheetIsOpened) {
NavigationStack {
List {
ScrollView {
ForEach(0..<100) { index in
VStack {
NavigationLink(destination: NavigatedView(currentSelection: $currentSelection)) {
Text("I have the index: \(index)")
.foregroundColor(.green)
}
}
.frame(maxWidth: .infinity)
}
}
.padding()
}
}
.presentationDetents([.medium, .large], selection: $currentSelection)
}
}
}
struct NavigatedView: View {
#Binding fileprivate var currentSelection: PresentationDetent
var body: some View {
ScrollView {
ForEach(0..<100) { index in
VStack {
Text("I'm a child and I have the index: \(index)")
.onAppear {
// DispatchQueue.main.asyncAfter(deadline: .now() + 0.6) {
currentSelection = .large
// }
}
}
.frame(maxWidth: .infinity)
}
}
.background(.red)
.padding()
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
I want to create some simple navigation, where when I press on a button from a list, I am taken to a specific page on a page style tab view.
My code is below:
struct NavView: View {
var body: some View {
NavigationView {
List {
NavigationLink(destination: PageView(tag: 0)) {
Text("0")
}
NavigationLink(destination: PageView(tag: 1)) {
Text("1")
}
NavigationLink(destination: PageView(tag: 2)) {
Text("2")
}
NavigationLink(destination: PageView(tag: 3)) {
Text("3")
}
NavigationLink(destination: PageView(tag: 4)) {
Text("4")
}
}
}
.tabViewStyle(.page(indexDisplayMode: .never))
}
}
struct PageView: View {
var tag: Int
var body: some View {
TabView {
ForEach(0..<5, id: \.self) { i in
Text("\(i)")
}
}
.tabViewStyle(.page(indexDisplayMode: .never))
}
}
As an example, if I wanted to go to the page labeled 3, I would like to use the click on the list option labeled 3 on the NavView to direct me to that page on PageView, which is being created with a ForEach. How would I be able to accomplish this? I hope my request made sense.
You can use the selection binding of TabView like this.
struct NavView: View {
var body: some View {
NavigationView {
List {
ForEach(0..<5, id: \.self) { i in
NavigationLink(destination: PageView(tag: i)) {
Text("\(i)")
}
}
}
}
}
}
struct PageView: View {
#State var tag: Int = 0
var body: some View {
TabView(selection: $tag) {
ForEach(0..<5, id: \.self) { i in
Text("\(i)")
.tag(i)
}
}
.tabViewStyle(.page(indexDisplayMode: .never))
}
}
So here's my problem:
I have a main view with a navigation view and then the list view.
The colors are just to distinguish different views.
At first everything works as intended but as soon as the app goes into the background (aka Home Screen) and then back to active again my list view is bugging out.
The whole list moves down roughly 3 times the height of one cell.
The main views background is blue so it's not the whole list which gets moved.
I tried to use PlainListStyle() and GroupedListStyle() but the header as well moves down.
If I use one of the links in the bottom blue bar (yeah I know, dumb choice of color and changed in the code snippet below) and then navigate back the list is back in it's original shape.
Edit:
Adding anything to the Stack above the list fixes the problem.
A text or a rectangle is all it takes.
As a workaround a rectangle with heigh 0.1 and the same color as the navigation bar is sufficient.
But any ideas to fix it permanently would be appreciated!
What it should look like:
What it looks like after the app has been in the background:
import SwiftUI
struct TestListView: View
{
#State var dummyList: [DummyCell] = []
#Environment(\.scenePhase) var scenePhase
#State private var listBlur : CGFloat = 0.0
var body: some View
{
List
{
ForEach(dummyList)
{ item in
item
.listRowBackground(Color.yellow)
}
.onDelete
{ indexSet in
dummyList.remove(atOffsets: indexSet)
}
.onMove
{ indices, newOffsets in
dummyList.move(fromOffsets: indices, toOffset: newOffsets)
}
}
.background
{
Color.green
}
.onAppear
{
if dummyList.isEmpty
{
let testData = testData()
testData.forEach
{
daten in
dummyList.append(daten)
}
}
}
.onDisappear { print("List OnDisappear") }
.blur(radius: listBlur)
.onChange(of: scenePhase)
{
newPhase in
if newPhase == .active
{
print("List Active")
dummyList.removeAll()
let list = testData()
list.forEach { cell in
dummyList.append(cell)
}
listBlur = 0
}
else if newPhase == .inactive
{
print("List Inactive")
listBlur = 10.0
}
}
}
private func testData() -> [DummyCell]
{
var list : [DummyCell] = []
for index in 0..<20
{
list.append(DummyCell(dummyNumber: index))
}
return list
}
}
struct TestListView_Previews: PreviewProvider {
static var previews: some View {
TestListView()
}
}
import SwiftUI
struct MainView: View {
#Environment(\.scenePhase) var scenePhase
var body: some View {
ZStack
{
NavigationView
{
VStack
{
// Main View
TestListView()
.navigationBarTitle("listTest", displayMode: .inline)
.navigationBarItems(leading: EditButton())
.navigationBarItems(trailing: EditButton())
// Footer
Footer()
}
.background
{
Color.green
}
}
.navigationBarColor(UIColor(Color.orange))
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
MainView()
}
}
struct Footer: View
{
var body: some View
{
HStack
{
NavigationLink(destination: Text("SomeView"), label: {
Text("SomeView")
})
.padding(.top)
.padding(.leading)
Spacer()
NavigationLink(destination: Text("SomeOtherView"), label: {
Text("SomeOtherView")
})
.padding(.top)
.padding(.trailing)
}
}
}
OUTLINE
I have 2 views, the first (view1) contains a HStack and an #ObservableObject. When the user selects a row from the HStack the #ObservableObject is updated to the string name of the row selected.
In view2 I have the same HStack as the first HStack in view1. This HStack observes #ObservableObject and desaturates all other rows except the one that matches the #ObservableObject.
PROBLEM
The HStack list in view2 is wider than the page so I would like to automatically scroll to the saturated/selected row when the view appears. I'm not totally sure how to use ScrollTo as it needs an integer and I am only storing/observing the string name.
VIEW 1
class selectedApplication: ObservableObject {
#Published var selectedApplication = "application1"
}
struct view1: View {
#ObservedObject var selectedOption = selectedApplication()
var applications = ["application1", "application2", "application3", "application4", "application5", "application6", "application7", "application8", "application9", "application10"]
var body: some View {
VStack{
ScrollView(.horizontal){
HStack{
ForEach(applications, id: \.self) { item in
Button(action: {
self.selectedOption.selectedApplication = item
}) {
VStack(alignment: .center){
Text(item)
}
}
}
}
}
}
}
}
View2:
struct View2: View {
#ObservedObject var application: selectedApplication
var applications = ["application1", "application2", "application3", "application4", "application5", "application6", "application7", "application8", "application9", "application10"]
var body: some View {
HStack{
ScrollView(.horizontal, showsIndicators: false) {
ScrollViewReader{ scroll in
HStack{
ForEach(applications, id: \.self) { item in
Button(action: {
application.selectedApplication = item
}) {
Text(item)
.saturation(application.selectedApplication == item ? 1.0 : 0.05)
}
}
}
}
}
}
}
}
You can use ScrollViewReader with the id that's being applied in the ForEach, so you don't actually need the row index number (although that, too, is possible to get, if you needed to, either by using enumerated or searching the applications array for the index of the item.
Here's my updated code:
struct ContentView : View {
#ObservedObject var selectedOption = SelectedApplicationState()
var body: some View {
VStack {
View1(application: selectedOption)
View2(application: selectedOption)
}
}
}
class SelectedApplicationState: ObservableObject {
#Published var selectedApplication = "application1"
var applications = ["application1", "application2", "application3", "application4", "application5", "application6", "application7", "application8", "application9", "application10"]
}
struct View1: View {
#ObservedObject var application: SelectedApplicationState
var body: some View {
VStack{
ScrollView(.horizontal){
HStack{
ForEach(application.applications, id: \.self) { item in
Button(action: {
self.application.selectedApplication = item
}) {
VStack(alignment: .center){
Text(item)
}
}
}
}
}
}
}
}
struct View2: View {
#ObservedObject var application: SelectedApplicationState
var body: some View {
HStack{
ScrollView(.horizontal, showsIndicators: false) {
ScrollViewReader{ scroll in
HStack{
ForEach(application.applications, id: \.self) { item in
Button(action: {
application.selectedApplication = item
}) {
Text(item)
.saturation(application.selectedApplication == item ? 1.0 : 0.05)
}
}
}.onReceive(application.$selectedApplication) { (app) in
withAnimation {
scroll.scrollTo(app, anchor: .leading)
}
}
}
}
}
}
}
How it works:
I just made a basic ContentView to show the two views, since I wasn't sure how they're laid out for you
ContentView owns the SelectedApplicationState (which was your selectionApplication ObservableObject (by the way, it is common practice to capitalize your type names -- that's why I changed the name. Plus, it was confusing to have a type and a property of that type with such a similar name) and passes it to both views.
SelectedApplicationState now holds the applications array, since it was being duplicated across views anyway
On selection in View1, selectedApplication in the ObservableObject is set, triggering onReceive in View2
There, the ScrollViewReader is told to scroll to the item with the id stored in selectedApplication, which is passed to the onReceive closure as app
In the event that these views are on separate pages, the position of View2 will still get set correctly once it is navigated to, because onReceive will fire on first load and set it to the correct position. The only requirement is passing that instance of SelectedApplicationState around.
I have a SwiftUI app with a basic List/Detail structure. A new item is created from
a modal sheet. When I create a new item and save it I want THAT list item to be
selected. As it is, if no item is selected before an add, no item is selected after
an add. If an item is selected before an add, that same item is selected after the
add.
I'll include code for the ContentView, but this is really the simplest example of
List/Detail.
struct ContentView: View {
#ObservedObject var resortStore = ResortStore()
#State private var addNewResort = false
#State private var coverDeletedDetail = false
#Environment(\.presentationMode) var presentationMode
var body: some View {
List {
ForEach(resortStore.resorts) { resort in
NavigationLink(destination: ResortView(resort: resort)) {
HStack(spacing: 20) {
Image("FlatheadLake1")
//bunch of modifiers
VStack(alignment: .leading, spacing: 10) {
//the cell contents
}
}
}
}
.onDelete { indexSet in
self.removeItems(at: [indexSet.first!])
self.coverDeletedDetail.toggle()
}
if UIDevice.current.userInterfaceIdiom == .pad {
NavigationLink(destination: WelcomeView(), isActive: self.$coverDeletedDetail) {
Text("")
}
}
}//list
.onAppear(perform: self.selectARow)
.navigationBarTitle("Resorts")
.navigationBarItems(leading:
//buttons
}//body
func removeItems(at offsets: IndexSet) {
resortStore.resorts.remove(atOffsets: offsets)
}
func selectARow() {
//nothing that I have tried works here
print("selectARow")
}
}//struct
And again - the add item modal is extremely basic:
struct AddNewResort: View {
//bunch of properties
var body: some View {
VStack {
Text("Add a Resort")
VStack {
TextField("Enter a name", text: $resortName)
//the rest of the fields
}
.textFieldStyle(RoundedBorderTextFieldStyle())
.padding(EdgeInsets(top: 20, leading: 30, bottom: 20, trailing: 30))
Button(action: {
let newResort = Resort(id: UUID(), name: self.resortName, country: self.resortCountry, description: self.resortDescription, imageCredit: "Credit", price: Int(self.resortPriceString) ?? 0, size: Int(self.resortSizeString) ?? 0, snowDepth: 20, elevation: 3000, runs: 40, facilities: ["bar", "garage"])
self.resortStore.resorts.append(newResort)
self.presentationMode.wrappedValue.dismiss()
}) {
Text("Save Trip")
}
.padding(.trailing, 20)
}
}
}
To show the issue - The list with a selection:
The list after a new item created showing the previous selection:
Any guidance would be appreciated. Xcode 11.4
I tried to reconstitute your code as closely as could so that it builds. Here is what I have in the end. We have a list of resorts and when a new resort is saved in the AddNewResort sheet, if we are currently in split view (horizontalSizeClass is regular), we will select the new resort, otherwise just dismiss the sheet.
import SwiftUI
class ResortStore: ObservableObject {
#Published var resorts = [Resort(id: UUID(), name: "Resort 1")]
}
struct ContentView: View {
#ObservedObject var resortStore = ResortStore()
#State private var addingNewResort = false
#State var selectedResortId: UUID? = nil
var navigationLink: NavigationLink<EmptyView, ResortView>? {
guard let selectedResortId = selectedResortId,
let selectedResort = resortStore.resorts.first(where: {$0.id == selectedResortId}) else {
return nil
}
return NavigationLink(
destination: ResortView(resort: selectedResort),
tag: selectedResortId,
selection: $selectedResortId
) {
EmptyView()
}
}
var body: some View {
NavigationView {
ZStack {
navigationLink
List {
ForEach(resortStore.resorts, id: \.self.id) { resort in
Button(action: {
self.selectedResortId = resort.id
}) {
Text(resort.name)
}
.listRowBackground(self.selectedResortId == resort.id ? Color.gray : Color(UIColor.systemBackground))
}
}
}
.navigationBarTitle("Resorts")
.navigationBarItems(trailing: Button("Add Resort") {
self.addingNewResort = true
})
.sheet(isPresented: $addingNewResort) {
AddNewResort(selectedResortId: self.$selectedResortId)
.environmentObject(self.resortStore)
}
WelcomeView()
}
}
}
struct ResortView: View {
let resort: Resort
var body: some View {
Text("Resort View for resort name: \(resort.name).")
}
}
struct AddNewResort: View {
//bunch of properties
#Binding var selectedResortId: UUID?
#State var resortName = ""
#Environment(\.presentationMode) var presentationMode
#Environment(\.horizontalSizeClass) var horizontalSizeClass
#EnvironmentObject var resortStore: ResortStore
var body: some View {
VStack {
Text("Add a Resort")
VStack {
TextField("Enter a name", text: $resortName)
//the rest of the fields
}
.textFieldStyle(RoundedBorderTextFieldStyle())
.padding(EdgeInsets(top: 20, leading: 30, bottom: 20, trailing: 30))
Button(action: {
let newResort = Resort(id: UUID(), name: self.resortName)
self.resortStore.resorts.append(newResort)
self.presentationMode.wrappedValue.dismiss()
if self.horizontalSizeClass == .regular {
self.selectedResortId = newResort.id
}
}) {
Text("Save Trip")
}
.padding(.trailing, 20)
}
}
}
struct WelcomeView: View {
var body: some View {
Text("Welcome View")
}
}
struct Resort {
var id: UUID
var name: String
}
We need to keep track of the selectedResortId
We create an invisible NavigationLink that will programmatically navigate to the selected resort
We make our list row a Button, so that the user can select a resort by tapping on the row
I started writing a series of articles about navigation in SwiftUI List view, there are a lot of points to consider while implementing programmatic navigation.
Here is the one that describes this solution that I'm suggesting: SwiftUI Navigation in List View: Programmatic Navigation. This solution works at the moment on iOS 13.4.1. SwiftUI is changing rapidly, so we have to keep on checking.
And here is my previous article that explains why a more simple solution of adding a NavigationLink to each List row has some problems at the moment SwiftUI Navigation in List View: Exploring Available Options
Let me know if you have questions, I'd be happy to help where I can.