returning swift protocol associated type in multiple methods - swift

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

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.

Swift Generics: Extending a non-generic type with a property of generic type where the generic parameter is the extended type

Problem
I have a type that takes one generic parameter that is required to inherit from UIView:
class Handler<View: UIView> {
...
}
Now, I want write a UIView extension to provide a property that returns Handler and uses Self as the generic parameter, so that in subclasses of UIView I'd always get the handler of type Handler<UIViewSubclass>:
extension UIView {
var handler: Handler<Self>? { return nil }
}
However this does not compile:
Covariant 'Self' can only appear at the top level of property type
I have also tried to define a protocol HandlerProvider first:
public protocol HandlerProvider {
associatedtype View: UIView
var handler: Handler<View>? { get }
}
(so far so good) and then extend UIView with that protocol:
extension UIView: HandlerProvider {
public typealias View = Self
public var handler: Handler<View>? { return nil }
}
But that does not compile either:
Covariant 'Self' can only appear as the type of a property, subscript or method result; did you mean 'UIView'?
Question
Is there a way in Swift to use Self as a generic parameter for properties in extension?
Here is possible approach (to think with generics in a bit different direction).
Tested with Xcode 11.4 / swift 5.2
// base handling protocol
protocol Handling {
associatedtype V: UIView
var view: V { get }
init(_ view: V)
func handle()
}
// extension for base class, will be called by default for any
// UIView instance that does not have explicit extension
extension Handling where V: UIView {
func handle() {
print(">> base: \(self.view)")
}
}
// extension for specific view (any more you wish)
extension Handling where V: UIImageView {
func handle() {
print(">> image: \(self.view)")
}
}
// concrete implementer
class Handler<V: UIView>: Handling {
let view: V
required init(_ view: V) {
self.view = view
}
}
// testing function
func fooBar() {
// handlers created in place of handling where type of
// handling view is know, so corresponding handle function
// is used
Handler(UIView()).handle()
Handler(UIImageView()).handle()
}
Output:

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.

Swift generics, cannot infer type T

I've tried a bunch of different things but i'm not good at generics.
How do I call a function with a variable that needs to be both a solid type like UIViewController, as well as conforming to a protocol like NavBarResponder ?
Thanks!
weak var lastNavBarResponder: UIViewController?
func reloadExtendedNavBar() {
self.updateState()
self.updateStatusBadges()
if let vc = self.lastNavBarResponder {
self.setup(viewController: vc) // Error: Generic parameter T cannot be inferred
}
}
func setup<T: UIViewController>(viewController: T) where T: NavBarResponder {
...
I suspect the error is because UIViewController doesn't naturally conform to NavBarResponder, so the compiler can't guarantee that lastNavBarResponder fits the requirements of setup.
Try changing lastNavBarResponder to be of a type that is a UIViewController that conforms to the NavBarResponder protocol.
Here is a self-contained example showing how this would be implemented.
protocol NavBarResponder {}
class NavBarResponderController: UIViewController {}
extension NavBarResponderController: NavBarResponder {}
var lastNavBarResponder: NavBarResponderController? = NavBarResponderController()
func setup<T: UIViewController>(viewController: T) where T: NavBarResponder {
print("Works")
}
func reloadExtendedNavBar() {
if let vc = lastNavBarResponder {
setup(viewController: vc)
}
}
reloadExtendedNavBar()