Provide a default implementation for a protocol method returning an associatedtype - swift

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#>
}
}

Related

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

How to declare a protocol with a function that returns NavigationLink

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>
}

SwiftUI - Returning an opaque type in a protocol

Currently, I have a protocol Media which has the method displaySummary() -> some View. The problem is, an opaque type cannot be returned in a protocol, as far as I know.
protocol Media {
func displaySummary() -> some View
}
The implementation code looks like the following:
final class Playlist: Media {
func displaySummary() -> some View {
return HStack {
Text("Summary")
.padding(.all)
.background(Color.black)
}
}
And in the ContentView, I have the following:
let media: Media = Playlist()
var body: some View {
ScrollView(.horizontal, showsIndicators: false) {
media.displaySummary()
}
}
Is there a way to make this work in SwiftUI?
Here is variant using protocol associatedtype, actually SwiftUI native approach, if we see in its auto-generated module. This allows to avoid type-erasure wrappers and use view directly.
Tested with Xcode 11.4 / iOS 13.4
Update: added ViewBulider, re-tested with Xcode 13.4 / iOS 15.5
protocol Media {
associatedtype Summary : View
func displaySummary() -> Self.Summary
}
final class Playlist: Media, Identifiable {
#ViewBuilder
func displaySummary() -> some View {
HStack {
Text("Summary")
.padding(.all)
.background(Color.black)
}
}
}
struct PlaylistView: View {
let playlist = Playlist()
var body: some View {
ScrollView(.horizontal, showsIndicators: false) {
playlist.displaySummary()
}
}
}
I was able to solve it using a type-erasure.
protocol Media {
func displaySummary() -> AnyView
}
Simple wrap the opaque View in AnyView:
final class Playlist: Media {
func displaySummary() -> AnyView {
return AnyView(HStack {
Text("Summary")
.padding(.all)
.background(Color.black))
})
}
}

Use protocol with constrained associated type as property in Swift

I'm trying to implement a datasource protocol with associated type
protocol DataSourceCompatible {
associatedtype CellModel
func cellModelForItem(at indexPath: IndexPath) -> CellModel
}
Protocol AddressBookViewModelType inherits from base protocol and constrains the associated value to another protocol
protocol AddressBookViewModelType: class, DataSourceCompatible where CellModel == AddressBookCellModelType {
}
AddressBookViewModel is a concrete implementation of AddressBookViewModelType protocol
class AddressBookViewModel: AddressBookViewModelType {
func cellModelForItem(at indexPath: IndexPath) -> AddressBookCellModelType {
let contact = sectionedContacts[indexPath.section][indexPath.row]
return AddressBookCellModel(contact: contact)
}
}
The code compiles fine, however when I declare the viewmodel as a property on my viewcontroller, the compiler fails with Protocol 'AddressBookViewModelType' can only be used as a generic constraint because it has Self or associated type requirements.
class AddressBookViewController: UIViewController {
private var viewModel: AddressBookViewModelType!
func configure(viewModel: AddressBookViewModelType) {
self.viewModel = viewModel
}
...
}
I remember seeing type erasure might solve the issue, but I'm not that familiar with type erasure concept. Is there a way to solve the issue?
Update:
How are AddressBookCellModelType and AddressBookCellModel related here?
It's a struct implementing a protocol.
protocol AddressBookCellModelType {
var name: String { get }
var photo: UIImage? { get }
var isInvited: Bool { get }
}
struct AddressBookCellModel: AddressBookCellModelType {
....
}
Have you tried just using it as a generic, like the warning/error says:
class AddressBookViewController<T: AddressBookViewModelType> : UIViewController {
private var viewModel: T!
func configure(viewModel: T) {
self.viewModel = viewModel
}
...
}
You'd need to initialise your controller with a property of variable T so the type can be inferred.
To expand on my questions in the comments, looking at this code it looks like it would be exactly as flexible without adding AddressBookCellModelType or AddressBookViewModelType, and this would also get rid of the headaches, while still being generic over DataSourceCompatible.
// This protocol is fine and very useful for making reusable view controllers. Love it.
protocol DataSourceCompatible {
associatedtype CellModel
func cellModelForItem(at indexPath: IndexPath) -> CellModel
}
// No need for a protocol here. The struct is its own interface.
// This ensures value semantics, which were being lost behind the protocol
// (since a protocol does not promise value semantics)
struct AddressBookCellModel {
var name: String
var photo: UIImage?
var isInvited: Bool
}
// AddressBookViewModel conforms to DataSourceCompatible
// Its conformance sets CellModel to AddressBookCellModel without needing an extra protocol
class AddressBookViewModel: DataSourceCompatible {
let sectionedContacts: [[AddressBookCellModel]] = []
func cellModelForItem(at indexPath: IndexPath) -> AddressBookCellModel {
return sectionedContacts[indexPath.section][indexPath.row]
}
}
class AddressBookViewController: UIViewController {
private var viewModel: AddressBookViewModel!
func configure(viewModel: AddressBookViewModel) {
self.viewModel = viewModel
}
}
Doing it this way allows for a generic VC without introducing more pieces that required:
class DataSourceViewController<DataSource: DataSourceCompatible>: UIView {
private var viewModel: DataSource.CellModel!
func configure(viewModel: DataSource.CellModel) {
self.viewModel = viewModel
}
}
let vc = DataSourceViewController<AddressBookViewModel>()
It's just Swift specification that you cannot use 'protocol with associated type' as a type declaration. The reason is that the compiler can't know what the associated type will actually be at the compile time, which violates the "type-safety" of Swift.
The solution would be to use type-eraser like you said, or make the type generic.