DEV Community

David Goyes
David Goyes

Posted on

SwiftUI #3: Modificadores en SwiftUI

Los modificadores de los controles, efectos, layout, texto, imagen y demás, son métodos que crean una nueva vista a partir de una existente. Se pueden encadenar tantos modificadores para personalizar una vista como sea necesario, teniendo en cuenta que algunas veces el orden aplicado importa.
A continuación presento algunos modificadores como ejemplo:

Relleno

padding(_:_:) agrega un relleno a ciertos bordes de la vista. Recibe:

  • edges: conjunto de bordes que se van a rellenar. Por defecto es all.
  • length: cantidad de puntos que se van a rellenar en un borde específico. Si se pasa nil, SwiftUI usa el valor por defecto de la plataforma (i.e. 8).
Text("Hola mundo")
  .padding(.leading, 200)
Enter fullscreen mode Exit fullscreen mode

Borde

border(_:width:) añade un borde a la vista con un estilo y grosor específico. Recibe:

Text("Hola mundo")
  .border(
    LinearGradient(
      colors: [.red, .blue],
      startPoint: UnitPoint(x: 0, y: 0.5),
      endPoint: UnitPoint(x: 1, y: 0.5)
    ), 
    width: 10
  )
// ...
Text("Hola mundo")
  .border(Color.systemBlue, width: 10)
Enter fullscreen mode Exit fullscreen mode

Encajar (estirar/repetir) imagen

resizable(capInsets:resizingMode:) define el modo usado por SwiftUI para encajar una imagen dentro de un espacio. Recibe:

  • capInsets: Valores de relleno que indican la porción de la imagen que no se va a encuadrar.
  • resizingMode: Modo usado para encuadrar la imagen. Por defecto es Image.ResizingMode.stretch.
Image(systemName: "pencil")
  .resizable()
Enter fullscreen mode Exit fullscreen mode

Encuadrar vista

frame(width:height:alignment:) posiciona una vista dentro de un marco invisible con el tamaño especificado. Recibe:

  • width: El ancho de la vista resultante. Si width es nil, la vista resultante toma el comportamiento de tamaño de la vista de entrada.
  • height: La altura de la vista resultante. Si height es nil, la vista resultante toma el comportamiento de tamaño de la vista de entrada.
  • alignment: Alineación de la vista dentro del recuadro resultante. Aparentemente no tiene ningún efecto si el tamaño de la vista es igual al recuadro. Por defecto es center.
Image(systemName: "pencil")
  .resizable()
  .frame(width: 200, height: 200)
Enter fullscreen mode Exit fullscreen mode

Colorear una vista

foregroundStyle(_:) define un estilo para una vista (View) de primer plano. Recibe:

Image(systemName: "pencil")
  .resizable()
  .foregroundStyle(
    .linearGradient(
      colors: [.yellow, .blue],
      startPoint: .top,
      endPoint: .bottom
    )
  )
Enter fullscreen mode Exit fullscreen mode

Rellenar una forma

fill(_:style:) rellena una forma (Shape) con un color. Recibe:

  • style: Las opciones de estilo que determinan como se rellena la forma.
Path { path in
    path.move(to: .zero)
    path.addLine(to: CGPoint(x: 50, y: 0))
    path.addArc(
        center: .zero,
        radius: 50,
        startAngle: .zero,
        endAngle: .degrees(90),
        clockwise: false)
}
.fill(.radialGradient(
    Gradient(colors: [.yellow, .red]),
    center: .topLeading,
    startRadius: 15,
    endRadius: 80))
Enter fullscreen mode Exit fullscreen mode

Aplicar un color de tinte

tint(_:) sirve para sobrescribir el color de acento predeterminado de una vista. Recibe:

  • tint: Color a aplicar.
Button {
  // Answer the call
} label: {
  Label("Answer", systemImage: "phone")
}
.tint(.green)
Enter fullscreen mode Exit fullscreen mode

Alerta

alert(_:isPresented:actions:message:) presenta una alerta con un mensaje cuando la condición data es verdadera, usando una vista Text como título. Recibe:

  • title: Título de la alerta.
  • isPresented: Un binding a valor booleano que determina si se debe presentar la alerta. Cuando el usuario presiona alguna de las acciones de la alerta, el sistema pone el valor de este binding en false y cierra la alerta. 
  • actions: Un ViewBuilder que retorna las acciones de la alerta.
  • message: Un ViewBuilder que retorna el mensaje de la alerta.
struct Login: View {
  @State private var didFail = false
  let alertTitle: String = "Login failed."
  var body: some View {
    LoginForm(didFail: $didFail)
      .alert(
        Text(alertTitle),
        isPresented: $didFail
      ) {
        Button("OK") {
          // Manejar la acción "OK"
        }
      } message: {
         Text("Please check your credentials and try again.")
      }
  }
}
Enter fullscreen mode Exit fullscreen mode

Sombra

shadow(color:radius:x:y:) agrega una sombra a la vista. Recibe:

  • color: El color de la sombra. Por defecto es negro con opacidad 0.33.
  • radius: Determina el cuán difuminada está la sobra.
  • x: Desplazamiento horizontal de la sombra con respecto a la vista. Por defecto es 0.
  • y: Desplazamiento vertical de la sombra con respecto a la vista. Por defecto es 0.
struct Shadow: View {
  let steps = [0, 5, 10]
  var body: some View {
    VStack(spacing: 50) {
      ForEach(steps, id: \.self) { offset in
        HStack(spacing: 50) {
          ForEach(steps, id: \.self) { radius in
            Color.blue
              .shadow(
                color: .primary,
                radius: CGFloat(radius),
                x: CGFloat(offset), y: CGFloat(offset))
              .overlay {
                VStack {
                  Text("\(radius)")
                  Text("(\(offset), \(offset))")
                }
              }
          }
        }
      }
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

Ignorar el área segura

ignoresSafeArea(_:edges:) expande el área segura de una vista. Recibe:

  • regions: Las regiones sobre las que se expande el área segura de una vista. El modificador se expande en todas las áreas por defecto.
  • edges: El conjunto de bordes a expandir. Todo borde que no se incluye permanecerá sin cambios. Por defecto, se incluyen todos los bordes.
ZStack {
  Color.red
    .ignoresSafeArea()
  Text("Hola")
}
Enter fullscreen mode Exit fullscreen mode

Poner una figura de fondo

background(in:fillStyle:) define el fondo de una vista para que sea una figura "insettable" rellena con el estilo "background" por defecto. Recibe:

ZStack {
  Color.teal
  Label("Flag", systemImage: "flag.fill")
    .padding()
    .background(in: RoundedRectangle(cornerRadius: 8))
}
Enter fullscreen mode Exit fullscreen mode

En el siguiente ejemplo, el texto tiene color negro mientras que la cápsula se pinta, pero no tiene color. Por esta razón, está pintando negro sobre negro y no se ve nada. La cápsula solo dibuja su forma, pero no aplica relleno si no se le indica uno.

VStack {
  Text("Hola mundo")
   .background(Capsule())
}
Enter fullscreen mode Exit fullscreen mode

Para solucionarlo hay que rellenar la cápsula:

Text("Hola mundo")
  .padding(.horizontal, 12)
  .padding(.vertical, 6)
  .background(
    Capsule().fill(.blue)
  )
  .foregroundColor(.white)
Enter fullscreen mode Exit fullscreen mode

Pintar con un margen dentro de un recuadro

inset(by:) retorna la misma forma de tipo InsettableShape retraída desde los bordes del rectángulo contenedor, desplazándola hacia adentro en cuanto al dibujo.
Si X es positivo: la forma se reduce (queda más pequeña). Si X es negativo: la forma "se expande" hacia afuera (es decir, se dibuja más grande que el contenedor original).

ZStack {
  Capsule()
    .fill(Color.blue)
  Capsule()
    .inset(by: 3)
    .fill(Color.red)
  // Como .fill retorna un ShapeView y .inset 
  // es un modificador de InsettableShape, entonces hay error
  // si se invierte el orden
}
Enter fullscreen mode Exit fullscreen mode

Orden de los modificadores

Cuando se aplica más de un modificador a una vista, algunas veces el orden importa.
Modificadores como padding y frame cambian la posición o forma de la vista. Modificadores como background o border rellenan o envuelven una vista. Normalmente se quiere definir la forma y posición de una vista antes de rellenarla (fill) o envolverla (wrap).

Text("Hola mundo")
  .padding()
  .border(Color.purple)
Enter fullscreen mode Exit fullscreen mode

En el ejemplo anterior se envuelve el texto de la vista Text con padding, y luego se pone un borde morado alrededor del texto con relleno. 
Si se aplica primero el borde, este rodeará el área intrínseca del texto. Luego, padding agrega cierto relleno alejando a los vecinos.

Text("Hola mundo")
  .border(Color.purple)
  .padding()
Enter fullscreen mode Exit fullscreen mode

Recordar que usualmente se quiere aplicar los modificadores que afectan la forma y posición de una vista, antes de rellenarla (fill) o envolverla (wrap).

Modificadores de tipos concretos de vistas

Algunos modificadores pueden ser aplicados a todos los tipos de vistas, mientras que otros solo pueden ser aplicados a algunos tipos como Text o Shape
Por otro lado, no todos los modificadores de Text retornan una vista tipo Text. Por ejemplo, font, fontWeight, bold e italic modifican una vista Text para producir otro Text. En este caso, no importa el orden de los modificadores.
Sin embargo, otros operadores como lineLimit retornan some View, así que el siguiente código arroja un error:

Text(guess.intString)
  .lineLimit(0)
  .bold()
Enter fullscreen mode Exit fullscreen mode

Mientras que el siguiente funciona correctamente:

Text(guess.intString)
  .bold()
  .lineLimit(0)
Enter fullscreen mode Exit fullscreen mode

Modificador personalizado

Se puede construir un modificador personalizado agregando una extensión al tipo de dato que se quiere modificar, y se tiene que devolver un tipo de dato específico. Por ejemplo, si se quiere modificar cualquier vista y retornar una vista con tipo opaco, se tiene que crear una extensión de View y retornar some View.

extension View {
  func miModificador(radius: CGFloat = 16, offset: CGFloat = 6) -> some View {
    return ...
  }
}
Enter fullscreen mode Exit fullscreen mode

Modificador de ambiente de nivel-de-vista

En ocasiones, aplicar un modificador a una vista puede afectar a todas sus hijas. Este tipo de modificador se conoce como view-level environment value. Por ejemplo, al aplicar font a un VStack, todas las vistas hijas se ven afectadas, por lo que todos los Text usarían headline como tamaño de fuente:

VStack {
  // ...
}
.font(.headline)
Enter fullscreen mode Exit fullscreen mode

Los modificadores de ambiente pueden ser modificados por una vista hija específica. Por ejemplo, en el siguiente ejemplo se puede sobreescribir el tamaño de fuente del Text:

VStack {
  Text("Hola mundo")
    .font(.subheadline)
  Text("Chao mundo")
}
.font(.headline)
Enter fullscreen mode Exit fullscreen mode

Top comments (0)