Swift UI navigationLink from item - ios

I have problem with making this itemView to navigationLink. I need onTapGesture to open next list
https://github.com/reddogwow/test/blob/main/MainMenu
var objectView: some View {
VStack {
Text(objectname)
.foregroundColor(.white)
.font(.system(size: 25, weight: .medium, design: .rounded))
Image(objectphoto)
.resizable()
.frame(width: 100, height: 100)
.clipShape(Circle())
.overlay(Circle().stroke(Color.white, lineWidth: 4))
}
.frame(height: 200)
.frame(maxWidth: .infinity)
.background(Color.blue)
}
Best edit will be where i can use Destination name from item (navMenu string)
I need something like this
var body: some View {
// NavigationView {
let columns = Array(
repeating: GridItem(.flexible(), spacing: spacing),
count: numbersOfColumns)
ScrollView {
HStack {
personView
petView
}
LazyVGrid(columns: columns, spacing: spacing) {
ForEach(items) { item in
NavigationLink(destination: item.navMenu) {
Text("")
} label: {
ItemView(item: item)
}
}
}
.padding(.horizontal)
}
.background(Color.blue.ignoresSafeArea())
.navigationTitle("")
// }
}
Where line NavigationLink(destination: HERE MUST BE STRING TO navMenu) But now im in cycle lot of fails
I have some menus called
Menu1.swift
Menu2.swift
Menu3.swift
I need open this menu after click on Grid menu.
But destination: Must be filled with name from item in code.
struct item: Identifiable {
let id = UUID()
let title: String
let image: String
let imgColor: Color
let navMenu : String
}
item(title: "Menu 1", image: "img1", imgColor: .orange, navMenu: "Menu1"),
I thing I have bad written buy maybe only small mistake
or maybe make it like this?
var navMenuDest = destination: + item.navMenu
this will be
NavigationLink(navMenuDest) {
in finale looks like
NavigationLink(destination: Menu1)

You must have a NavigationView in the hierarchy to use NavigationLink. To make each ItemView navigate to a new view when tapped, we use NavigationLink as shown below.
Code:
struct MainMenu: View {
/* ... */
var body: some View {
NavigationView {
let columns = Array(
repeating: GridItem(.flexible(), spacing: spacing),
count: numbersOfColumns)
ScrollView {
HStack {
personView
objectView
}
LazyVGrid(columns: columns, spacing: spacing) {
ForEach(items) { item in
NavigationLink {
Text("Some destination view here...\n\nItem: \(String(describing: item))")
} label: {
ItemView(item: item)
}
}
}
.padding(.horizontal)
}
.background(Color.blue.ignoresSafeArea())
.navigationTitle("Main Menu")
}
}
}
Result:

Related

How to hide TabView in NavigationLink?

First of all I did a full research for my problem and NOTHING on Google helped me,
the problem is simple, it seems that the solution should also be simple, but I can't hide the tabbar in NavigationLink, and if something works out, then wierd behavior of the buttons and the transition back, etc...
TabView itself
import SwiftUI
struct Main: View {
#State var currentTab: Int = 0
var body: some View {
TabView(selection: $currentTab) {
HomeView().tag(0)
AccountInfoView().tag(1)
SettingsView().tag(2)
}
.tabViewStyle(.page(indexDisplayMode: .never))
.edgesIgnoringSafeArea(.bottom)
.overlay(
TabBarView(currentTab: $currentTab),
alignment: .top
)
}
}
struct TabBarView: View {
#Binding var currentTab: Int
#Namespace var namespace
var tabBarOptions: [String] = ["Search", "Items", "Account"]
var body: some View {
HStack(spacing: 0) {
ForEach(Array(zip(self.tabBarOptions.indices,
self.tabBarOptions)),
id: \.0,
content: {
index, name in
TabBarItem(currentTab: self.$currentTab,
namespace: namespace.self,
tabBarItemName: name,
tab: index)
})
}
.padding(.top)
.background(Color.clear)
.frame(height: 100)
.edgesIgnoringSafeArea(.all)
}
}
struct TabBarItem: View {
#Binding var currentTab: Int
let namespace: Namespace.ID
var tabBarItemName: String
var tab: Int
var body: some View {
Button {
self.currentTab = tab
} label: {
VStack {
Spacer()
Text(tabBarItemName)
if currentTab == tab {
CustomColor.myColor
.frame(height: 2)
.matchedGeometryEffect(id: "underline",
in: namespace,
properties: .frame)
} else {
Color.clear.frame(height: 2)
}
}
.animation(.spring(), value: self.currentTab)
}
.fontWeight(.heavy)
.buttonStyle(.plain)
}
}
NavigationLink -> this is just the part of the code that contains the NavigationLink, this VStack of course is inside the NavigationView.
struct HomeView: View {
NavigationView {
...
VStack(spacing: 15) {
ForEach(self.data.datas.filter {(self.search.isEmpty ? true : $0.title.localizedCaseInsensitiveContains(self.search))}, id: \.id) { rs in
NavigationLink(
destination: ItemDetails(data: rs)
){
RecentItemsView(data: rs)
}
.buttonStyle(PlainButtonStyle())
}
}
}
}
ItemDetails
struct ItemDetails: View {
let data: DataType
var body : some View {
NavigationView {
VStack {
AsyncImage(url: URL(string: data.pic), content: { image in
image.resizable()
}, placeholder: {
ProgressView()
})
.aspectRatio(contentMode: .fill)
.frame(width: 250, height: 250)
.clipShape(RoundedRectangle(cornerRadius: 12.5))
.padding(10)
VStack(alignment: .leading, spacing: 8, content: {
Text(data.title)
.fontWeight(.bold)
.frame(maxWidth: .infinity, alignment: .center)
Text(data.description)
.font(.caption)
.foregroundColor(.gray)
.frame(maxWidth: .infinity, alignment: .center)
})
.padding(20)
}
.padding(.horizontal)
}
.navigationBarBackButtonHidden(true)
}
}
I apologize for the garbage in the code, it seemed to me that there is not much of it and it does not interfere with understanding the code, also during the analysis of this problem on the Google\SO, I did not need to work with other parts of the code anywhere, except for those that I provided above, but if I missed something, then please let me know, thanks.

SwiftUI List only taps content

I have a List in SwiftUI that I populate with a custom SwiftUI cell, the issue is that on tap I need to do some stuff and the tap only works when you click the text in the cell, if you click any empty space it will not work. How can I fix this?
struct SelectDraftView: View {
#Environment(\.presentationMode) var presentationMode
#ObservedObject var viewModel = SelectDraftViewModel()
var body: some View {
VStack {
List {
ForEach(viewModel.drafts.indices, id: \.self) { index in
DraftPostCell(draft: viewModel.drafts[index])
.contentShape(Rectangle())
.onTapGesture {
presentationMode.wrappedValue.dismiss()
}
}
.onDelete { indexSet in
guard let delete = indexSet.map({ viewModel.drafts[$0] }).first else { return }
viewModel.delete(draft: delete)
}
}
.background(Color.white)
Spacer()
}
}
}
struct DraftPostCell: View {
var draft: CDDraftPost
var body: some View {
VStack(alignment: .leading) {
Text(draft.title ?? "")
.frame(alignment: .leading)
.font(Font(UIFont.uStadium.helvetica(ofSize: 14)))
.padding(.bottom, 10)
if let body = draft.body {
Text(body)
.frame(alignment: .leading)
.multilineTextAlignment(.leading)
.frame(maxHeight: 40)
.font(Font(UIFont.uStadium.helvetica(ofSize: 14)))
}
Text(draft.date?.toString(format: "EEEE, MMM d, yyyy") ?? "")
.frame(alignment: .leading)
.font(Font(UIFont.uStadium.helvetica(ofSize: 12)))
}
.padding(.horizontal, 16)
}
}
try adding .frame(idealWidth: .infinity, maxWidth: .infinity) just after DraftPostCell(...). You can also use a minWidth: if required.
EDIT-1: the code I use for testing (on real devices ios 15.6, macCatalyst, not Previews):
import Foundation
import SwiftUI
struct ContentView: View {
var body: some View {
SelectDraftView()
}
}
class SelectDraftViewModel: ObservableObject {
#Published var drafts: [
CDDraftPost] = [
CDDraftPost(title: "item 1", date: Date(), body: "body 1"),
CDDraftPost(title: "item 2", date: Date(), body: "body 4"),
CDDraftPost(title: "item 3", date: Date(), body: "body 3")]
func delete(draft: CDDraftPost) { }
}
struct CDDraftPost: Codable {
var title: String?
var date: Date?
var body: String?
}
struct SelectDraftView: View {
#Environment(\.presentationMode) var presentationMode
#ObservedObject var viewModel = SelectDraftViewModel()
var body: some View {
VStack {
List {
ForEach(viewModel.drafts.indices, id: \.self) { index in
DraftPostCell(draft: viewModel.drafts[index])
.frame(idealWidth: .infinity, maxWidth: .infinity, alignment: .leading)
.contentShape(Rectangle())
.border(.red) // <-- for testing
.onTapGesture {
print("----> onTapGesture")
// presentationMode.wrappedValue.dismiss()
}
}
.onDelete { indexSet in
guard let delete = indexSet.map({ viewModel.drafts[$0] }).first else { return }
viewModel.delete(draft: delete)
}
}
.background(Color.white)
Spacer()
}
}
}
struct DraftPostCell: View {
var draft: CDDraftPost
var body: some View {
VStack(alignment: .leading) {
Text(draft.title ?? "")
.frame(alignment: .leading)
// .font(Font(UIFont.uStadium.helvetica(ofSize: 14)))
.padding(.bottom, 10)
if let body = draft.body {
Text(body)
.frame(alignment: .leading)
.multilineTextAlignment(.leading)
.frame(maxHeight: 40)
// .font(Font(UIFont.uStadium.helvetica(ofSize: 14)))
}
Text(draft.date?.formatted() ?? "")
.frame(alignment: .leading)
// .font(Font(UIFont.uStadium.helvetica(ofSize: 12)))
}
.padding(.horizontal, 16)
}
}
I'm probably late but this will be useful for anyone checking this in the future.
You need to add .background() modifier to your view before you do .onTapGesture{...}
so in your ForEach code would be modified like this:
ForEach(viewModel.drafts.indices, id: \.self) { index in
DraftPostCell(draft: viewModel.drafts[index])
.contentShape(Rectangle())
.frame(maxWidth: .infinity) // you should use the frame parameter according to your needs, but if you want your cell to occupy the whole width of your scroll view, use this one
.background() // this makes the empty portions of view 'non-transparent', so those portions also receive the tap gesture
.onTapGesture {
presentationMode.wrappedValue.dismiss()
}
}
P.S if you need the whole portion of your scroll view cell to receive the tap gesture you'll also need to add .frame(...) modifier, so it has the exact background you want

List selection not changing value

I want to change a #State var variable whenever a row in a list is being selected. Similar to what didSelectRow does in UIKit. That means when tapping on "One" the text on the right-hand side should change from "Nothing selected" to "One".
Here's my current implementation. However, tapping on a row does nothing at all.
struct ContentView: View {
#State var items = [Item(id: 1, text: "One"), Item(id: 2, text: "Two"), Item(id: 3, text: "Three")]
#State var selectedItem: Item? = nil
var body: some View {
GeometryReader { geometry in
HStack(alignment: .center) {
List(items, selection: $selectedItem) { item in
Text(item.text)
}
.frame(width: geometry.size.width / 2.5, height: geometry.size.height)
.listStyle(InsetGroupedListStyle())
Text(selectedItem?.text ?? "Nothing selected")
}
}
}
}
struct Item: Identifiable, Hashable {
var id: Int
var text: String
}
How can I change the text when someone taps a row?
Selection in List works only if EditMode is in active state, so you need to handle it manually, something like
var body: some View {
GeometryReader { geometry in
HStack(alignment: .center) {
List(items) { item in
Text(item.text)
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .leading)
.contentShape(Rectangle())
.onTapGesture {
selectedItem = item
}
}
.frame(width: geometry.size.width / 2.5, height: geometry.size.height)
.listStyle(InsetGroupedListStyle())
Text(selectedItem?.text ?? "Nothing selected")
}
}
}
and if needed to highlight background than also do it manually.
You can use Button inside your List, and then select the Item in their action like this
List(items, id:\.self, selection: $selectedItem) { item in
Button(action: {
self.selectedItem = item
})
{
Text(item.text)
}
}

Buttons inside list item don't work properly

I have 2 views: PollCard and PollList (like list of polls)
In the PollCard view I have 2 buttons(images), that calls "answer" function:
HStack{
Button(action: {
self.answer()
print("Pressed first image")
}){
Image(poll.v1img)
.resizable()
.renderingMode(.original)
.scaledToFill()
.frame(width: 150, height: 200)
}.frame(width: 150, height: 200)
Button(action: { self.answer()}){
Image(poll.v2img )
.resizable()
.renderingMode(.original)
.frame(width: 150, height: 200)
}.frame(width: 150, height: 200).zIndex(4)
}
In the PollList view I have this simple list:
var body: some View {
HStack{
List(pollData) { poll in
PollCard(poll: poll)
}.padding()
}
}
But when I click the images in the list, it selects like all images and presses it
It is also very easy to check - terminal prints Pressed first image even if I've pressed only second image
What should I do to fix this?
As I mentioned in the comment section the workaround would be to substitute the HStack around the List with a ScrollView and the List with a ForEach:
struct ContentView: View {
struct Data: Identifiable {
var id: Int
}
#State var data = [Data(id: 0), Data(id: 1), Data(id: 2), Data(id: 3), Data(id: 4), Data(id: 5)]
var body: some View {
ScrollView {
ForEach(self.data) { data in
HStack {
Button(action: {
print("Pressed blue...")
}, label: {
Rectangle()
.foregroundColor(Color.blue)
.frame(width: 150, height: 200)
})
Button(action: {
print("Pressed red...")
}, label: {
Rectangle()
.foregroundColor(Color.red)
.frame(width: 150, height: 200)
})
}
}
}
}
}
I hope this helps!
this is an unexpected behavior of Button (or it is a bug?) in current SwiftUI (either on macOS or iOS).
The workaround in your case is simple, try to apply PlainButtonStyle for your buttons
import SwiftUI
struct ContentView: View {
var body: some View {
List {
Text("Hello, World!")
HStack {
Button(action: {
print("button1")
}) {
Color.yellow
}.buttonStyle(PlainButtonStyle())
Button(action: {
print("button2")
}) {
Color.green
}.buttonStyle(PlainButtonStyle())
}
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
The funny thing is that it is enough to apply the style on one button only ... or apply it to parent HStack :-)
changing ContentView ...
struct ContentView: View {
var body: some View {
List {
Text("Hello, World!")
HStack {
Button(action: {
print("button1")
}) {
Color.yellow
}
Button(action: {
print("button2")
}) {
Color.green
}
}
.buttonStyle(PlainButtonStyle())
.frame(height: 100)
Text("By by, World!")
}
}
}
you get
where each of buttons works as expected

SwiftUI Jump Menu (sectionIndexTitles)

I have an alphabetically sorted SwiftUI List with sections. I try to set a jump menu / sidebar for scrolling through the sections like here:
In UIKit it was possible by setting the sectionIndexTitles. But I couldn't find a solution for SwiftUI.
What you can do in SwiftUI 2.0 is to use the ScrollViewReader.
First, in your list, each element has to have a unique ID which can be any hashable object. Simply use the .id modifier. For example:
List(0..<10) { i in
Text("Row \(i)")
.id(i)
}
After that you can use the ScrollViewReader as following:
ScrollViewReader { scroll in
VStack {
Button("Jump to row #10") {
scroll.scrollTo(10)
}
List(0..<10) { i in
Text("Row \(i)")
.id(i)
}
}
}
So in your case you could give the each alphabetical section an id, so section "a" would have .id(a) etc. After that you could use the sidebar you've implemented and jump to the desired alphabetical section inside ScrollViewReader.
Edit: So I have tried to make a very simple solution quickly. It is not perfect but it serves your purpose. Feel free to copy and modify the code:
struct AlphaScroller: View {
let alphabet = ["a","b","c","d","e","f","g","h","i","j","k","l","m","n","o","p","q","r","s","t","u","v","w","x","y","z"]
let sampleItems = ["Apple", "Banana", "Orange"]
var body: some View {
ScrollView {
ScrollViewReader{ scroll in
VStack {
//top character picker with horizontal Scrolling
ScrollView(.horizontal) {
HStack(spacing: 20) {
ForEach(alphabet, id: \.self) { char in
Button(char){
withAnimation {
scroll.scrollTo(char, anchor: .top)
}
}
}
}
}.frame(width: UIScreen.main.bounds.width * 0.8, height: 20)
//list of sections
ForEach(alphabet, id: \.self){ char in
HStack {
VStack {
Text(char).id(char)
.font(.system(size: 30))
.padding()
ForEach(sampleItems, id: \.self){ item in
Text(item)
.padding()
}
Rectangle()
.foregroundColor(.gray)
.frame(width: UIScreen.main.bounds.width, height: 1)
}
Spacer()
}
}
}
}
}
}
}

Resources