How to add horizontal carousel above List in SwiftUI - swiftui

I'm trying to recreate the layout of the native Watch app which has a carousel with a List underneath it. How can get the carousel in the first section to scroll off the edges of the screen instead of cut off?
Here's the code I'm trying:
let data: [String] = (0..<25).map { "Item \($0)" }
let data2: [String] = (0..<3).map { "Item \($0)" }
NavigationView {
List {
Section {
ScrollView(.horizontal) {
HStack {
ForEach(data) { element in
VStack {
Text(element)
}
.padding()
.background(Color(.secondarySystemBackground))
.cornerRadius(10)
}
}
}
}
.listRowBackground(Color.clear)
.listRowInsets(.init())
Section("Section 1") {
ForEach(data2) { element in
NavigationLink {
Text("Destination")
} label: {
Label(element, systemImage: "calendar.circle.fill")
}
}
}
}
.searchable(text: .constant(""), placement: .navigationBarDrawer(displayMode: .always))
.navigationTitle("Watch")
}
This is the native watch app I'm trying to replicate:

Related

SwiftUI DataStructure for ForEach.. (how to delete)

I'm working on an app which show images when tapping buttons.
I have a 4 buttons, and Each buttons has a action that add a image.
The problem I have is that when I remove by ontapGesture, The deleting order is not my want.
Ex) button 1 -> button 2 -> button 3 -> button 4 : Now I can see 4 images in vstack, plus, minus, multiply, divide.
But If I tap button 1 now, the plus image still there.
I think the problem arises because of array. What dataStructure should I use instead?
`
import SwiftUI
struct ContentView: View {
#State var array: [Int] = []
let systemDictionary: [Int : Image] = [
0 : Image(systemName: "plus.circle"),
1 : Image(systemName: "minus.circle"),
2 : Image(systemName: "multiply.circle"),
3 : Image(systemName: "divide.circle")
]
var body: some View {
VStack {
HStack {
Button {
array.append(0)
} label: {
Text("Button 0")
}
Button {
array.append(1)
} label: {
Text("Button 1")
}
Button {
array.append(2)
} label: {
Text("Button 2")
}
Button {
array.append(3)
} label: {
Text("Button 3")
}
}
ForEach(array.indices, id: \.self) { index in
systemDictionary[index]
.font(.system(size: 30))
.padding()
.onTapGesture {
array.remove(at: index)
}
}
}
}
}
`
I found out when I tap button 1 again, it delete the data in array[0].
But there is still array[0], because array always has index.
I understand! but I can not come up with data structure for images.
Using your current setup, you could try this approach, using the content of the array array[index] for the index into systemDictionary, and to limit the set of images to 4 only, use if !array.contains(0) { array.append(0) } as shown in the code:
struct ContentView: View {
#State var array: [Int] = []
let systemDictionary: [Int : Image] = [
0 : Image(systemName: "plus.circle"),
1 : Image(systemName: "minus.circle"),
2 : Image(systemName: "multiply.circle"),
3 : Image(systemName: "divide.circle")
]
var body: some View {
VStack {
HStack {
Button {
if !array.contains(0) { array.append(0) } // <-- here
} label: {
Text("Button 0")
}
Button {
if !array.contains(1) { array.append(1) }
} label: {
Text("Button 1")
}
Button {
if !array.contains(2) { array.append(2) }
} label: {
Text("Button 2")
}
Button {
if !array.contains(3) { array.append(3) }
} label: {
Text("Button 3")
}
}
ForEach(array.indices, id: \.self) { index in
systemDictionary[array[index]] // <-- here
.font(.system(size: 30))
.padding()
.onTapGesture {
array.remove(at: index)
}
}
}
}
}
EDIT-1: using a specific structure for the images:
struct Picture: Identifiable {
let id = UUID()
var image: Image
var label: String
var isVisible: Bool = false
}
struct ContentView: View {
#State var pictures: [Picture] = [
Picture(image: Image(systemName: "plus.circle"), label: "Button 0"),
Picture(image: Image(systemName: "minus.circle"), label: "Button 1"),
Picture(image: Image(systemName: "multiply.circle"), label: "Button 2"),
Picture(image: Image(systemName: "divide.circle"), label: "Button 3")
]
var body: some View {
VStack {
HStack {
ForEach($pictures) { $picture in
Button {
picture.isVisible = true
} label: {
Text(picture.label)
}
}
}
ForEach($pictures) { $picture in
if picture.isVisible {
picture.image
.font(.system(size: 30))
.padding()
.onTapGesture {
picture.isVisible = false
}
}
}
}
}
}

How to avoid the .searchable from appearing and disappearing?

I am having a lot of buggy behavior on .searchable, on iOS 16.1 (Xcode 14.1). As you can see in the screenshot below. When entering a view with a .searchable component it will overlap with the view in transition and then disappears.
I am trying to make the code as basic as possible.
struct ContentView: View {
var body: some View {
NavigationStack {
List {
NavigationLink {
Mailbox().navigationTitle("Inkomend")
} label: { Label("Inkomend", systemImage: "tray") }
NavigationLink {
Mailbox().navigationTitle("Verstuurd")
} label: { Label("Verstuurd", systemImage: "paperplane") }
NavigationLink {
Mailbox().navigationTitle("Prullenmand")
} label: { Label("Prullenmand", systemImage: "trash") }
}
.navigationTitle("Postbussen")
.refreshable {}
}
}
}
struct Mailbox: View {
#State private var searchQuery: String = ""
var body: some View {
List {
NavigationLink {
Text("Detail")
} label: {
VStack(alignment: .leading) {
Text("Apple").font(.headline)
Text("Verify your account.")
Text("Fijn dat je deze belangrijke stap neemt om je account te verifiëren.").lineLimit(2).foregroundColor(.secondary)
}
}
}
.searchable(text: $searchQuery)
}
}
Use NavigationView instead of NavigationStack.
Like Latin Bhuva said, the new NavigationStack is not intended to be used in this way, a NavigationView would be more correct in this case.
struct ContentView: View {
var body: some View {
NavigationView {
List {
NavigationLink {
Mailbox().navigationTitle("Inkomend")
} label: { Label("Inkomend", systemImage: "tray") }
NavigationLink {
Mailbox().navigationTitle("Verstuurd")
} label: { Label("Verstuurd", systemImage: "paperplane") }
NavigationLink {
Mailbox().navigationTitle("Prullenmand")
} label: { Label("Prullenmand", systemImage: "trash") }
}
.navigationTitle("Postbussen")
.refreshable {}
}
}
}

new navigation stack swiftui 4

Hello I'm using the new navigation stack and I don't really understand how it works in a situation like mine. I have the first view (DashboardView) from there I have two navigationdestination(isPresented) and from the second one I have to go a view (AllVocabulariesView) with a list and after every row has to go to another view (VocabularyView) that is the save that I can reach from the DashboardView with the first navigationdestination(isPresented).
here below the DashboardView (I remove unnecessary code)
NavigationStack {
VStack {
LazyVGrid(columns: columns, alignment: .center, spacing: spacing, pinnedViews: [])
{
ForEach(vmHome.vocabularies.prefix(5)) { vocabulary in
VocabularyBannerView(name: vocabulary.name ?? "", language: vmHome.getLanguageName(code: vocabulary.language ?? ""), isFavourite: vocabulary.isFavourite)
.onTapGesture {
selectedVocabulary = vocabulary
showVocabularyView.toggle()
}
.contextMenu {
Button {
vmHome.setVocabularyAsFavourite(entity: vocabulary)
} label: {
Label("Favourite", systemImage: "star")
}
Button(role: .destructive) {
vmHome.deleteVocabulary(vocabulary: vocabulary)
} label: {
Label("Delete", systemImage: "trash")
}
} preview: {
VocabularyBannerView(name: vocabulary.name ?? "", language: vmHome.getLanguageName(code: vocabulary.language ?? ""), isFavourite: vocabulary.isFavourite)
}
}
}
Button {
showAllVocabularyView.toggle()
} label: {
Text("Show all")
.font(.caption)
}
}
.navigationTitle("Dashboard")
.toolbar(.hidden, for: .navigationBar)
.navigationDestination(isPresented: $showVocabularyView) {
if selectedVocabulary != nil {
VocabularyView(vmHome: vmHome, vocabulary: selectedVocabulary!)
}
}
.navigationDestination(isPresented: $showAllVocabularyView) {
AllVocabulariesView(vmHome: vmHome)
}
}
so the first one goes to the VocabularyView and it's fine also the second goest to AllVocabulariesView but now the problem is inside the AllVocabulariesView that I have structured like this
NavigationStack{
List {
ForEach(vmHome.vocabularies) { vocabulary in
NavigationLink(value: vocabulary) {
Text(vocabulary.name ?? "")
}
}
}
.navigationDestination(for: VocabularyEntity.self, destination: { vocabulary in
VocabularyView(vmHome: vmHome, vocabulary: vocabulary)
})
.navigationTitle("Your vocabularies")
.navigationBarTitleDisplayMode(.inline)
}
two thinks if I remove the navigationstack the row has being highlighted but nothing happen and with navigationstack it goest to the view but without animation and the back button goest back to DashboardView instead of AllVocabulariesView
After testing in several applications, I have come to the conclusion that NavigationStack should not be nested.
In fact, you can use the .navigationDestination at the top level and it will keep working for all nested NavigationLink.
Although I cannot test without the full code, I suggest you do the following modifications:
Remove the NavigationStack from your AllVocabulariesView
Group your .navigationDestination at the top level where you have put the other ones
DashboardView:
NavigationStack {
VStack {
LazyVGrid(columns: columns, alignment: .center, spacing: spacing, pinnedViews: [])
{
ForEach(vmHome.vocabularies.prefix(5)) { vocabulary in
VocabularyBannerView(name: vocabulary.name ?? "", language: vmHome.getLanguageName(code: vocabulary.language ?? ""), isFavourite: vocabulary.isFavourite)
.onTapGesture {
selectedVocabulary = vocabulary
showVocabularyView.toggle()
}
.contextMenu {
Button {
vmHome.setVocabularyAsFavourite(entity: vocabulary)
} label: {
Label("Favourite", systemImage: "star")
}
Button(role: .destructive) {
vmHome.deleteVocabulary(vocabulary: vocabulary)
} label: {
Label("Delete", systemImage: "trash")
}
} preview: {
VocabularyBannerView(name: vocabulary.name ?? "", language: vmHome.getLanguageName(code: vocabulary.language ?? ""), isFavourite: vocabulary.isFavourite)
}
}
}
Button {
showAllVocabularyView.toggle()
} label: {
Text("Show all")
.font(.caption)
}
}
.navigationTitle("Dashboard")
.toolbar(.hidden, for: .navigationBar)
.navigationDestination(isPresented: $showVocabularyView) {
if selectedVocabulary != nil {
VocabularyView(vmHome: vmHome, vocabulary: selectedVocabulary!)
}
}
.navigationDestination(isPresented: $showAllVocabularyView) {
AllVocabulariesView(vmHome: vmHome)
}
.navigationDestination(for: VocabularyEntity.self, destination: { vocabulary in
VocabularyView(vmHome: vmHome, vocabulary: vocabulary)
})
}
AllVocabulariesView:
List {
ForEach(vmHome.vocabularies) { vocabulary in
NavigationLink(value: vocabulary) {
Text(vocabulary.name ?? "")
}
}
}
.navigationTitle("Your vocabularies")
.navigationBarTitleDisplayMode(.inline)

How to adjust SwiftUI List row separator inset with a Label like Settings app?

In SwiftUI, I haven't found an elegant method with SwiftUI List api to let List row separator be like settings app style, while the row including a Image and a Text (aka Label).
What I want is:
What I usually do:
```swift
// here is my common code
NavigationView {
List {
NavigationLink {
Text("Detail 1")
} label: {
Label {
Text("Row Title 1")
} icon: {
Image(systemName: "gear")
}
}
NavigationLink {
Text("Detail 3")
} label: {
Label {
Text("Row Title 3")
} icon: {
Image(systemName: "wifi")
}
}
NavigationLink {
Text("Detail 2")
} label: {
Label {
Text("Row Title 2")
} icon: {
Image(systemName: "person")
}
}
}
.listStyle(.insetGrouped)
.navigationTitle("Demo")
}
```

SwiftUI: How to set the title for the toolbar on macOS 11?

I have a SwiftUI app that has two columns and a toolbar. I'm trying to emulate the latest macOS applications and use the toolbar in the same way that Mail does, i.e. select a sidebar item and show that as the title of the window.
Here is my code:
import SwiftUI
var listItems = ["Item 1", "Item 2", "Item 3", "Item 4"]
var secondItems = ["Second 1", "Second 2", "Second 3", "Second 4"]
struct ContentView: View
{
#State var select: String? = "Item 1"
var body: some View
{
VStack
{
NavigationView
{
List
{
ForEach((0..<listItems.count), id: \.self)
{index in
NavigationLink(destination: SecondView(), tag: listItems[index], selection: $select)
{
Text(listItems[index])
.padding(.vertical, 2.0)
}
}
Spacer()
}.frame(width:160)
}
.toolbar
{
Text("this is not the title")
Button(action: {})
{
Label("Upload", systemImage: "square.and.arrow.up")
}
}
}
}
}
struct SecondView: View
{
var body: some View
{
NavigationView
{
List
{
ForEach((0..<secondItems.count), id: \.self)
{index in
NavigationLink(destination: Text(secondItems[index]))
{
Text(secondItems[index])
.frame(height: 20)
}
}
}.frame(width:150)
}
}
}
So I get a toolbar like this:
but how do I set the title of the window in the toolbar when I select an item in the first list?
Here is how I solved it:
import SwiftUI
var listItems = ["Item 1", "Item 2", "Item 3", "Item 4"]
var secondItems = ["Second 1", "Second 2", "Second 3", "Second 4"]
struct ContentView: View
{
#State var select: String? = "Item 1"
var body: some View
{
VStack
{
NavigationView
{
List
{
ForEach((0..<listItems.count), id: \.self)
{index in
NavigationLink(destination: SecondView(), tag: listItems[index], selection: $select)
{
Text(listItems[index])
.frame(height: 20)
}
}
Spacer()
}
.listStyle(SidebarListStyle())
}
.toolbar
{
Text("this is not the title")
Button(action: {})
{
Label("Upload", systemImage: "square.and.arrow.up")
}
}
.navigationTitle(select!)
.navigationSubtitle("\(listItems.count) records")
.navigationViewStyle(DoubleColumnNavigationViewStyle())
}
}
}
struct SecondView: View {
var body: some View {
NavigationView {
List
{
ForEach((0..<secondItems.count), id: \.self)
{index in
NavigationLink(destination: Text(secondItems[index]))
{
Text(secondItems[index])
.frame(height: 20)
}
}
}
}
}
}
HStack(spacing: 0) {
ToolView()
Divider()
PanelView()
}
.frame(width: 290)
.toolbar(id: "id") {
ToolbarItem(id: "sidbar", placement: .primaryAction) {
Button {
} label: {
Label {
Text("Stack")
} icon: {
Image("stack")
.resizable()
.aspectRatio(contentMode: .fit)
.frame(width: 20, height: 20)
.offset(y: 10)
}
}
}
}
Use id to create ToolbarItem and you can show the title with right click on the toolbar.
Use Button to show the content, and the label and image will fit the toolbar.
NOTE: if you have multiple toolbars, make sure to provide each one with an id, or you still won't be able to show the title.
It is best to use SF Symbols, but if you use a custom font, make sure to resize it, and offset it.
If you are attempting to change the text that is currently set to "toolbar", you want to set the navigation title. This is done via .navigationTitle(String) and .navigationSubtitle(String).