I would like to create a factory method for NavigationLink:s as follows:
func makeNavigationLink(label: String, destination: View) -> some View {
NavigationLink(destination: StatsView(), label: {
Text(label)
.foregroundColor(.white)
.font(.title)
})
}
This produces an error: Protocol 'View' can only be used as a generic constraint because it has Self or associated type requirements
How should this be coded?
Add view constraint.
func makeNavigationLink<Destination: View>(label: String, destination: Destination) -> some View {
NavigationLink(destination: StatsView(), label: {
Text(label)
.foregroundColor(.white)
.font(.title)
})
}
Related
This question already has an answer here:
Swiftui How to pass a view that takes a parameter to another view that will set the parameter when calling the view
(1 answer)
Closed last month.
I am trying to make a NavigationLink and provide the destination in its init but I am receiving an error:
Type 'any View' cannot conform to 'View'
struct MenuButton: View {
let iconName: String
let destination: () -> any View
var body: some View {
NavigationLink { //Type 'any View' cannot conform to 'View'
destination()
} label: {
Image(systemName: iconName)
.foregroundColor(.pink)
.padding()
}
}
}
struct MenuBar: View {
var body: some View {
HStack {
MenuButton(iconName: "gearshape") {
//providing destination here
let user = User(firstName: "Mock", lastName: "Data", dateStarted: 142356345)
return HomeView(viewModel: HomeViewModel(user: user))
}
}
}
}
If I switch any View to some View in the destination declaration, I receive an error:
Property declares an opaque return type, but has no initializer expression from which to infer an underlying type
You just need to make MenuButton generic over some type (say V) that conforms to View:
struct MenuButton<V: View>: View {
let iconName: String
let destination: () -> V
var body: some View {
NavigationLink {
destination()
} label: {
Image(systemName: iconName)
.foregroundColor(.pink)
.padding()
}
}
}
Another option I found is wrapping destination() in AnyView:
struct MenuButton: View {
let iconName: String
let destination: () -> any View
var body: some View {
NavigationLink {
AnyView(destination())
} label: {
Image(systemName: iconName)
.foregroundColor(.pink)
.padding()
}
}
}
I have a Picker within a Form. I don't want to show any label on this Picker, so I set the label to EmptyView(). Though, the UI still presents an empty space where the label would be. I'm looking for a way to remove this space.
var body: some View {
NavigationView{
Form {
...
Section(header: Text("Emails")){
HStack{
Picker(selection: $phoneType, label: EmptyView()){
ForEach(phoneTypes, id: \.self){
Text($0)
}
}
.pickerStyle(.navigationLink)
Spacer()
Link("my#email.com", destination: URL(string: "mailto:my#email.com")!)
}
Button("Add Email") {}
}
...
}
}
.navigationTitle("John Doe")
}
The correct approach is to use labelsHidden() to hide the label of Picker.
Picker(selection: $phoneType, label: EmptyView()){
ForEach(phoneTypes, id: \.self){
Text($0)
}
}
.labelsHidden()
.pickerStyle(.navigationLink)
A possible workaround is using Picker inside Menu:
HStack{
Menu {
Picker("", selection: $phoneType){
ForEach(["Work","Home","Cell"], id: \.self){ type in
Button(type) { phoneType = type }
}
}
} label: {
Text(phoneType).foregroundColor(.secondary)
}
Spacer()
Link("my#email.com", destination: URL(string: "mailto:my#email.com")!)
}
Put the Text in the loop inside an HStack and add a trailing Spacer
var body: some View {
NavigationView{
Form {
...
Section(header: Text("Emails")){
HStack{
Picker(selection: $phoneType, label: EmptyView()){
ForEach(phoneTypes, id: \.self){ t in
HStack {
Text(t)
Spacer()
}
}
}
.pickerStyle(.navigationLink)
Spacer()
Link("my#email.com", destination: URL(string: "mailto:my#email.com")!)
}
Button("Add Email") {}
}
...
}
}
.navigationTitle("John Doe")
}
I have a TextField on a View. Getting below warning on tapping of a text field. Not sure Why? Below is the code used.
This is view where button is available to click. On click of this button, Bottom view will be displayed.
struct ContentView: View {
#State var cardShown = false
#State var cardDismissal = false
var body: some View {
NavigationView {
ZStack {
Button(action: {
cardShown.toggle()
cardDismissal.toggle()
}, label: {
Text("Show Card")
.bold()
.foregroundColor(Color.white)
.background(Color.blue)
.frame(width: 200, height: 50)
})
BottomCard(cardShown: $cardShown, cardDismissal: $cardDismissal, height: 400, content: {
CardContent()
.padding()
})
}
}
}
}
This is the bottom view where the text field exists. On this text field click, getting error.
struct CardContent: View {
#State private var text = ""
var body: some View {
VStack {
Text("Photo Collage")
.bold()
.font(.system(size: 30))
.padding()
Text("You can create awesome photo grids and share them with all of your friends")
.font(.system(size: 18))
.multilineTextAlignment(.center)
TextEditor(text: $text)
.frame(height: 100)
}
.padding()
}
}
Generic View.
struct BottomCard<Content: View>: View {
let content: Content
#Binding var cardShown: Bool
#Binding var cardDismissal: Bool
let height: CGFloat
init(cardShown: Binding<Bool>, cardDismissal: Binding<Bool>, height: CGFloat, #ViewBuilder content: () -> Content) {
_cardShown = cardShown
_cardDismissal = cardDismissal
self.height = height
self.content = content()
}
var body: some View {
ZStack {
// Dimmed
GeometryReader { _ in
EmptyView()
}
.background(Color.gray.opacity(0.5))
.opacity(cardShown ? 1: 0)
.animation(Animation.easeIn, value: 0.9)
.onTapGesture {
// Dismiss
dismiss()
}
// Card
VStack {
Spacer()
VStack {
content
Button(action: {
// Dismiss
dismiss()
}, label: {
Text("Dismiss")
.foregroundColor(Color.white)
.frame(width: UIScreen.main.bounds.width/2, height: 50)
.background(Color.pink)
.cornerRadius(8)
})
.padding()
}
//.background(Color(UIColor.secondarySystemBackground))
.background(Color.yellow)
.frame(height: height)
.offset(y: (cardShown && cardShown) ? 0 : 800)
.animation(Animation.default.delay(0.2), value: 0.2)
.padding(.bottom, 300)
}
}
.edgesIgnoringSafeArea(.all)
}
func dismiss() {
cardDismissal.toggle()
//self.view.endEditing(true)
DispatchQueue.main.asyncAfter(deadline: .now()+0.25){
cardShown.toggle()
}
}
}
Getting below error while tapping on textField.
objc[9303]: Class _PointQueue is implemented in both /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Library/Developer/CoreSimulator/Profiles/Runtimes/iOS.simruntime/Contents/Resources/RuntimeRoot/System/Library/PrivateFrameworks/UIKitCore.framework/UIKitCore (0x129df7a50) and /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Library/Developer/CoreSimulator/Profiles/Runtimes/iOS.simruntime/Contents/Resources/RuntimeRoot/System/Library/PrivateFrameworks/TextInputUI.framework/TextInputUI (0x13c7b68d8). One of the two will be used. Which one is undefined.
objc[9303]: Class _PathPoint is implemented in both /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Library/Developer/CoreSimulator/Profiles/Runtimes/iOS.simruntime/Contents/Resources/RuntimeRoot/System/Library/PrivateFrameworks/UIKitCore.framework/UIKitCore (0x129df7a78) and /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Library/Developer/CoreSimulator/Profiles/Runtimes/iOS.simruntime/Contents/Resources/RuntimeRoot/System/Library/PrivateFrameworks/TextInputUI.framework/TextInputUI (0x13c7b68b0). One of the two will be used. Which one is undefined.
I was also looking for an answer on this and it appears it could just be 'log noise'. I found the following in a different question.
Apple developer Quinn “The Eskimo!” # Developer Technical Support # Apple answered this question here:
This is not an error per se. Rather, it’s the Objective-C runtime
telling you that:
Two frameworks within your process implement the same class (well, in
this case classes, namely _PathPoint and _PointQueue).
The runtime will use one of them, choosing it in an unspecified way.
This can be bad but in this case it’s not. Both of the implementations
are coming from the system (well, the simulated system) and thus you’d
expect them to be in sync and thus it doesn’t matter which one the
runtime uses.
So, in this specific case, these log messages are just log noise.
I'm trying to implement a delete feature for a list of elements but the onDelete event doesn't work as expected.
Here's the code:
#ObservedObject var dm: DataManager = DataManager.shared
#State var isAddShown = false
var body: some View {
NavigationView {
VStack {
List($dm.TTDItemList) { $item in
VStack(alignment: .leading) {
TextEditor(text: $item.itemDesc)
.onTapGesture {}
Text(item.ItemTagsToText())
.font(.caption)
.foregroundColor(Color.red)
.multilineTextAlignment(.leading)
}
}
.onDelete(perform: cancella)
}
.onTapGesture { hideKeyboardAndSave() }
.navigationBarTitle(Text("Board"), displayMode: .inline)
.navigationBarItems(trailing:
Button(action: { withAnimation { self.isAddShown.toggle() } }) {
Image(systemName: !isAddShown ? "plus.circle.fill" : "minus.circle.fill").imageScale(.large)
}
)
}
}
private func hideKeyboardAndSave() {
UIApplication.shared.sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil)
dm.salva()
}
func cancella(at offset: IndexSet) {
guard let intindex = Array(offset).first else { return }
dm.TTDItemList.remove(at: intindex)
dm.salva()
}
}
The error I receive is:
Value of type 'List<Never, ForEach<LazyMapSequence<Array.Indices, (Array.Index, UUID)>, UUID, VStack<TupleView<(some View, some View)>>>>' (aka 'List<Never, ForEach<LazyMapSequence<Range, (Int, UUID)>, UUID, VStack<TupleView<(some View, some View)>>>>') has no member 'onDelete'
Implementing the List was tricky, but the code seems working allowing saving the modifications in the data manager.
The onDelete modifier is a method of ForEach.. It is not defined for other Views like List. You need to modify your code to use a ForEach inside your List.
List {
ForEach($dm.TTDItemList) { $item in
VStack(alignment: .leading) {
TextEditor(text: $item.itemDesc)
.onTapGesture {}
Text(item.ItemTagsToText())
.font(.caption)
.foregroundColor(Color.red)
.multilineTextAlignment(.leading)
}
}
.onDelete(perform: cancella)
}
Technically, onDelete is an extension method of the DynamicViewContent protocol, but the only types that conform to DynamicViewContent are ForEach and modified derivatives of ForEach.
I can't use multiple NavigationLinks in the same row of a List.
It looks like the navigation stack is totally messed up, because you tap once and it goes to multiple views and back erratically...
in TestList, I've tried adding the separate NavigationLinks in Sections, and I've tried moving the NavigationLinks two different places in the view hierarchy...
I've tried adding two NavigationViews for each row of the list, but
then the navigationTitleBar don't go away when I need it to..
struct ContentView: View {
var body: some View {
NavigationView {
TestList()
}
}
}
struct TestList: View {
var body: some View {
List {
ListCellView()
}
}
}
struct ListCellView: View {
var body: some View {
VStack {
Spacer()
NavigationLink(destination: TestDestination1()) {
Text("Test Destination 1")
.frame(width: 140, height: 50)
.background(RoundedRectangle(cornerRadius: 7.0).strokeBorder(Color.green, lineWidth: 3.0))
}
Spacer()
NavigationLink(destination: TestDestination2()) {
Text("Test Destination 2")
.frame(width:140, height: 50)
.background(RoundedRectangle(cornerRadius: 7.0).strokeBorder(Color.purple, lineWidth: 3.0))
Spacer()
}
}
}
}
struct TestDestination1: View {
var body: some View {
Text("Test Destination 1")
}
}
struct TestDestination2: View {
var body: some View {
Text("Test Destination 2")
}
}
I expect that when you tap a NavigationLink, it will navigate to the destination view.
What happens is when two NavigationLinks are in the same row of a List and you tap in it, it will:
1. go to one of the views
2. After tapping 'back', it will take you back to the view AND THEN take you to the other destination view.
https://youtu.be/NCTnqjzJ4VE
As the others mentioned, why 2 NavigationLinks in 1 cell. The issue is with multiple buttons and gesture in general for the Cell. I guess it is expected 1 Button/NavigationLink max per cell. As you noticed, on your video, you tap on a NavigationLink but your full Cell got the gesture (highlighted), which in return impact the other Buttons/NavigationLinks.
Anyhow, you can have it working, the 2 NavigationLinks in 1 cell, with a hack. Below I have created SGNavigationLink, which I use for my own app which solve your issue. It simply replace NavigationLink and based on TapGesture, so you will lose the highlight.
NB: I slightly modified your ListCellView, as the Spacer within my SGNavigationLink was creating an internal crash.
struct ListCellView: View {
var body: some View {
VStack {
HStack{
SGNavigationLink(destination: TestDestination1()) {
Text("Test Destination 1")
.frame(width: 140, height: 50)
.background(RoundedRectangle(cornerRadius: 7.0).strokeBorder(Color.green, lineWidth: 3.0))
}
Spacer()
}
HStack{
SGNavigationLink(destination: TestDestination2()) {
Text("Test Destination 2")
.frame(width:140, height: 50)
.background(RoundedRectangle(cornerRadius: 7.0).strokeBorder(Color.purple, lineWidth: 3.0))
}
Spacer()
}
}
}
}
struct SGNavigationLink<Content, Destination>: View where Destination: View, Content: View {
let destination:Destination?
let content: () -> Content
#State private var isLinkActive:Bool = false
init(destination: Destination, title: String = "", #ViewBuilder content: #escaping () -> Content) {
self.content = content
self.destination = destination
}
var body: some View {
return ZStack (alignment: .leading){
if self.isLinkActive{
NavigationLink(destination: destination, isActive: $isLinkActive){Color.clear}.frame(height:0)
}
content()
}
.onTapGesture {
self.pushHiddenNavLink()
}
}
func pushHiddenNavLink(){
self.isLinkActive = true
}
}
I am not sure about why do you need multiple Navigationlinks (duplicate code). You can use a data source that will hold the required properties of the list [title, color, id etc] and based on the id, call the desired View. Reuse the same code. Here is an example.
struct TestList: View {
var body: some View {
List { // <- Use Data source
ForEach(0..<2) { index in
ListCellView(index: index)
}
}
}
}
struct ListCellView: View {
var index: Int
var body: some View {
return NavigationLink(destination: ViewFactory.create(index)) {
Text("Test Destination 1")
.frame(width: 140, height: 50)
.background(RoundedRectangle(cornerRadius: 7.0).strokeBorder(Color.green, lineWidth: 3.0))
}
}
}
class ViewFactory {
static func create(_ index: Int) -> AnyView {
switch index {
case 0:
return AnyView(TestDestination1())
case 1:
return AnyView(TestDestination2())
default:
return AnyView(EmptyView())
}
}
}
struct TestDestination1: View {
var body: some View {
Text("Test Destination 1")
}
}
struct TestDestination2: View {
var body: some View {
Text("Test Destination 2")
}
}