How to declare a protocol with a function that returns NavigationLink - swift

I want to declare a protocol that has function which must return NavigationLink. But when I try this it returns an error "Reference to generic type 'NavigationLink' requires arguments in <...>"
protocol Protocol: class{
func function() -> NavigationLink
}
(Jessy)
class BeersListRouter: BeersListRouterProtocol{
typealias Label = Text
typealias Destination = View
func getBeerDetailsView(for beer: Beer) -> NavigationLink<Label, Destination>{
}
}

NavigationLink is a generic type, with two placeholders. You need to account for them.
protocol Protocol: AnyObject {
associatedtype Label: View
associatedtype Destination: View
func function() -> NavigationLink<Label, Destination>
}

Related

Provide a default implementation for a protocol method returning an associatedtype

I have this basic protocol:
protocol MyProtocol {
associatedtype Content: View
#MainActor #ViewBuilder func content() -> Content
}
I want to provide a default implementation for content(), so I figured I'd do this:
extension MyProtocol {
#MainActor #ViewBuilder func content() -> some View {
EmptyView()
}
}
That works. However, now autocompletion in conforming types does something strange. Instead of making the return type some View, it uses Content, which does not compile:
struct Implementation: MyProtocol {
func content() -> Content { // Auto-completed, does not compile
Text("ABC")
}
}
I have to manually fix the return type:
struct Implementation: MyProtocol {
func content() -> some View { // Now it compiles
Text("ABC")
}
}
Is there a way to fix this behavior?
As stated in the comment section, the issue is that the associatedtype Content is not yet defined when you are trying to use autocompletion.
If you define Content with a typealias before implementing the content() func autocompletion will behave as expected. ie:
struct Implementation: MyProtocol {
typealias Content = Text
func content() -> Text {
<#code#>
}
}

How we can access a needed protocol in other protocol in Swift?

So I was looking to ViewModifier protocol in SwiftUI I see the requirement of:
associatedtype Body: View
#ViewBuilder func body(content: Self.Content) -> Self.Body
typealias Content
Then I could not understand how even it is possible?! because we cannot reference Content to Self, simply it does not defined. How that code should build? I am 100% that I am missing something here but cannot find it. I see typealias Content in documentation, but as you can see it make no meaning for Xcode.
public protocol CustomViewModifier {
associatedtype Body: View
#ViewBuilder func body(content: Self.Content) -> Self.Body
// typealias Content // What should be Content here?
}
Error:
'Content' is not a member type of type 'Self'
So how can I define Content to my protocol like apple did for ViewModifier?
Update:
struct TestCustomViewModifier: CustomViewModifier {
func body(content: Content) -> some View {
return content
}
}
Error:
Return type of instance method 'body(content:)' requires that 'TestCustomViewModifier.Content' (aka '_MyContent') conform to 'View'
It looks like following:
public protocol CustomViewModifier {
associatedtype Body: View
#ViewBuilder func body(content: Self.Content) -> Self.Body
typealias Content = _MyContent<Self>
}
public struct _MyContent<Modifier> where Modifier: CustomViewModifier {
}
The only difference is that underscored names in SwiftUI namespace, like SwiftUI._ViewModifierContent are hidden by Xcode as private API.

returning swift protocol associated type in multiple methods

Coming from a java/kotlin background I am trying to get my head around swift protocols with associated types. Why does the following snippet fail to compile with error: "Type ViewFactoryImpl does not conform to protocol ViewFactory" and what would be the solution?
protocol ViewFactory {
associatedtype V: View
func createSomeView() -> V
func createAnotherView() -> V
}
class ViewFactoryImpl: ViewFactory {
func createSomeView() -> some View {
return ViewA()
}
func createAnotherView() -> some View {
return ViewB()
}
}
By playing around i was able to compile by defining the following instead
protocol ViewFactory {
associatedtype V1: View
associatedtype V2: View
func createSomeView() -> V1
func createAnotherView() -> V2
}
I do not understand the issue. I thought the associated type is defined for the entire protocol and can be used in multiple methods. What I am missing?
Each associatedtype refers to one type.
ViewA and ViewB are not the same type.
some does not turn them both into the same type.
And even if they were backed by the same type, you can't use multiple opaque return types to match a single associated type.
protocol ViewFactory {
associatedtype View: SwiftUI.View
func createSomeView() -> View
func createAnotherView() -> View
}
// Type 'ViewFactoryImpl' does not conform to protocol 'ViewFactory'
class ViewFactoryImpl: ViewFactory {
func createSomeView() -> some View {
ViewA()
}
func createAnotherView() -> some View {
ViewA()
}
}
The closest you can get to that is to use a type alias. It carries semantic meaning but does nothing to obscure the underlying types.
class ViewFactoryImpl: ViewFactory {
typealias View = ViewA
func createSomeView() -> View {
ViewA()
}
func createAnotherView() -> View {
ViewA()
}
}
The solution you came up with by playing around is exactly what you need
As mentioned elsewhere, the main issue with your first protocol is that you're enforcing createSomeView() createAnotherView() both return the same type. While ViewA() and ViewB() are both candidates for V, since they conform to View they are still different types, and therefore cannot BOTH be V in a given object.
By defining both V1 and V2, you allow for each function to return a different type, or the same type, it's all acceptable. By making both V1 and V2 require View conformance, you allow for the some View syntax

Swift error: cannot convert from 'A' to 'A?' when using generics

The problem is this:
I want to create a class using generics. That class has a delegate that contains functions that take values of the generic type as arguments. The delegate is declared using a protocol.
For some reason, I cannot get this to compile in Swift 5. This is the code:
protocol SelectItemViewControllerDelegate: AnyObject {
func selectItemViewController<T>(_ vc: SelectItemViewController<T>, selectedItem: T) where T: CustomStringConvertible
}
class SelectItemViewController<T> where T: CustomStringConvertible {
weak var delegate: SelectItemViewControllerDelegate?
}
class MyClass: SelectItemViewControllerDelegate {
private var selectItemCompletionBlock: ((String?) -> Void)?
func selectItemViewController<String>(_ vc: SelectItemViewController<String>, selectedItem: String) {
selectItemCompletionBlock?(selectedItem)
}
}
The error I get is:
error: cannot convert value of type 'String' to expected argument type 'String?'
selectItemCompletionBlock?(selectedItem)
^
as! String
When I do this:
selectItemCompletionBlock?(selectedItem as? String)
I get
error: cannot convert value of type 'String?' to expected argument type 'Swift.String?'
selectItemCompletionBlock?(selectedItem as? String)
^
as! String
What is going on here?
The String here:
func selectItemViewController<String>
declares a generic parameter called String. It does not refer to Swift.String, which selectItemCompletionBlock can accept.
Since SelectItemViewController is generic, you want its delegate to specify for which T it is implementing the delegate. In the case of MyClass, it is String. To do this, you need an associated type, not a generic method:
protocol SelectItemViewControllerDelegate {
associatedtype ItemType: CustomStringConvertible
func selectItemViewController(_ vc: SelectItemViewController<ItemType>, selectedItem: ItemType)
}
Now MyClass can implement selectItemViewController(_:selectedItem:) just for ItemType == String:
class MyClass: SelectItemViewControllerDelegate {
private var selectItemCompletionBlock: ((String?) -> Void)?
func selectItemViewController(_ vc: SelectItemViewController<String>, selectedItem: String) {
selectItemCompletionBlock?(selectedItem)
}
}
The problem now becomes, this is now invalid
weak var delegate: SelectItemViewControllerDelegate?
You can't use a protocol with associated types as the type of a property. To solve this, you need to create a type eraser:
struct AnySelectItemViewControllerDelegate<T: CustomStringConvertible>: SelectItemViewControllerDelegate {
let selectedItemFunc: (SelectItemViewController<T>, T) -> Void
init<DelegateType: SelectItemViewControllerDelegate>(_ del: DelegateType) where DelegateType.ItemType == T {
// keep a weak reference to del here, so as not to cause a retain cycle
selectedItemFunc = { [weak del] x, y in del?.selectItemViewController(x, selectedItem: y) }
}
func selectItemViewController(_ vc: SelectItemViewController<T>, selectedItem: T) {
selectedItemFunc(vc, selectedItem)
}
}
Then, you can declare the delegate as:
var delegate: AnySelectItemViewControllerDelegate<T>?
And to assign the delegate, create an AnySelectItemViewControllerDelegate. Suppose self is the delegate...
selectItemVC.delegate = AnySelectItemViewControllerDelegate(self)
That's a lot of work isn't it? If you only have few delegate methods, I suggest not using a delegate. Just declare some closure properties in SelectItemVC:
var onItemSelected: ((T) -> Void)?

How to make struct and typealias conform to #objc

Is it possible to make a struct and/or typealias conform to #objc? I wish to create optional protocol functions, one returns a struct, the other a typealias.
public typealias SwiperData = (image: UIImage, title: String)
public struct SwiperPeekViewControllers{
public var parentViewController: UIViewController!
public var contentViewController: UIViewController!
public init(parentVC: UIViewController, contentVC: UIViewController){
parentViewController = parentVC
contentViewController = contentVC
}
}
protocol
#objc public protocol SwiperPeekViewDelegate: class{
func didUndoAction(index: Int, dataSource: SwiperData)
// Method cannot be a member of an #objc protocol because the type of the parameter 2 cannot be represented in Objective-C
func swiperPeekViewControllers()->SwiperPeekViewControllers
func swiperPeekViewSize()->CGSize
}
You can't export typealiases to Objective-C. And you can't do it for tuples. But, about typealias, it's still possible to write Obj-C typedef for some struct and use it from Swift, it will be exported as typealias