Qué es View

View es el protocolo base que debe implementar cualquier componente que tenga una representación visual. Al ser un protocolo no podemos declararlo directamente.

Aquí podéis consultar la documentación oficial

SwiftUI basa todo su funcionamiento en este protocolo. Como hemos indicado anteriormente cualquier elemento visual deberá implementar este protocolo. El protocolo tiene la siguiente definición.

public protocol View {

    /// The type of view representing the body of this view.
    ///
    /// When you create a custom view, Swift infers this type from your
    /// implementation of the required `body` property.
    associatedtype Body : View

    /// The content and behavior of the view.
    var body: Self.Body { get }
}

Esta definición nos impide utilizar el protocolo View como un tipo de retorno, ya que consta de un tipo asociado Body que debe ser conocido antes de poder usar dicho protocolo. Este tipo Body es de tipo View y concretamente es igual a la vista que se devuelve el parámetro body.

Para entender esto probad el siguiente código:

struct ContentView: View {
    var body: View {
        Text("Hello world!")
    }
}

Este código devolverá el siguiente error: Protocol 'View' can only be used as a generic constraint because it has Self or associated type requirements. Lo que indica este error es que no se conoce los tipos asociados que contiene el protocolo View, concretamente el tipo asociado Body. Una forma de resolver este problema sería especificando concretamente el tipo de retorno.

//Bad solution
struct ContentView: View {
    var body: Text {
        Text("Hello world!")
    }
}

Este sería un mal ejemplo de como resolver el problema. Si lo hacemos de esta forma tendremos muchos problemas cuando tengamos una vista compleja, ya que tendremos que indicar todos los tipos anidados uno dentro de otros.

//Bad solution
struct ContentView: View {
    var body: Group<Text> {
        Group {
            Text("Hello world!")
        }
    }
}

Esta solución es inviable ya que nos obligaría a mantener relación entre el tipo devuelto y su implementación y si cambiaramos la implementación tendríamos que cambiar el tipo devuelto. Si aún no estáis convencidos probad a declarar una vista más compleja e intenar indicar el tipo devuelto para el parámetro body.

Para resumir, no podemos usar el genérico View porque necesitamos definir el tipo asociado y la solución anteriormente propuesta tampoco es viable. ¿Qué podemos hacer? Para este caso se han desarrollado los tipos opacos.

Tipos opacos

Los tipos opacos permiten ocultar el tipo concreto de dato que se devuelve de una función para que tenga soporte a los protocolos. Esto quiere decir que a efectos de implementación podremos usar un protocolo como tipo devuelto, pero en tiempo de compilación el compilador conocerá el tipo concreto que se devuelve. Esto obliga a que todos los métodos de una función devuelvan siempre el mismo tipo en todos los return que esta contenga.

En el caso de SwiftUI esto es muy útil ya que usar un tipo opaco en la variable body deja que el compilador indicar el tipo de retorno real de la variable, definiendo el tipo asociado del protocolo View (como hemos hecho en las dos malas implementaciones anteriores), por lo que nos permitiría usar el protocolo View como tipo devuelto y de esta forma será el propio compilador quien infiera el tipo real que se está devolviendo.

El uso de los tipos opacos se realiza declarando el tipo de retorno como some <Type>. Para este ejemplo el resultado sería el siguiente.

struct ContentView: View {
    var body: some View {
        Group {
            Text("Hello world!")
        }
    }
}