Avatar ·

Как изменить размер изображения (Image) в SwiftUI: масштабирование и кадрирование

Всем привет! Я только начал изучать SwiftUI и столкнулся с проблемой: никак не могу нормально изменить размер изображения. В UIKit я привык использовать imageView.contentMode и imageView.frame, а в SwiftUI всё иначе.

У меня есть изображение, которое я загружаю из Assets:

struct ContentView: View {
    var body: some View {
        Image("myPhoto")
    }
}

Проблема в том, что картинка отображается в оригинальном размере (4000x3000 пикселей) и вылезает за границы экрана. Я пробовал использовать .frame(width: 300, height: 200), но изображение просто обрезается, а не масштабируется. Пробовал разные модификаторы, но ничего не работает как надо.

Подскажите, пожалуйста, как правильно изменить размер изображения в SwiftUI? Мне нужно:

  1. Просто уменьшить картинку до определённого размера (например, 300x200) с сохранением пропорций.
  2. Заполнить контейнер изображением, обрезав лишнее по краям (как AspectFill).
  3. Уместить изображение целиком внутри контейнера (как AspectFit).

Буду благодарен за примеры кода и объяснение, как работают модификаторы .resizable(), .aspectRatio(), .scaledToFit() и .scaledToFill(). В документации Apple написано сложно, а на практике ничего не получается.

Заранее спасибо!

<<>>

Основные модификаторы для изменения размера Image в SwiftUI

В SwiftUI управление размером изображения строится на трёх ключевых модификаторах: .resizable(), .aspectRatio() / .scaledToFit() / .scaledToFill() и .frame(). Разберём каждый на примерах.

1. .resizable() — обязательный первый шаг

Без этого модификатора Image всегда рисуется в оригинальном размере. .resizable() позволяет изображению растягиваться или сжиматься под заданный контейнер:

Image("myPhoto")
    .resizable() // теперь изображение можно масштабировать
    .frame(width: 300, height: 200)

⚠️ Если просто добавить .frame(), изображение может исказиться, так как по умолчанию SwiftUI растягивает его без сохранения пропорций.

2. Сохранение пропорций: .aspectRatio(), .scaledToFit(), .scaledToFill()

Эти модификаторы решают проблему искажения:

  • .scaledToFit() — вписывает изображение целиком в заданный прямоугольник, сохраняя пропорции. Свободное пространство заполняется фоном.
  • .scaledToFill() — заполняет весь прямоугольник, обрезая лишнее по краям (как AspectFill в UIKit).
  • .aspectRatio(contentMode: .fit) — то же, что .scaledToFit().
  • .aspectRatio(contentMode: .fill) — то же, что .scaledToFill().

Примеры:

// Вариант 1: Уместить целиком (AspectFit)
Image("myPhoto")
    .resizable()
    .scaledToFit()
    .frame(width: 300, height: 200)
    .background(Color.gray) // чтобы видеть свободное пространство

// Вариант 2: Заполнить контейнер с обрезкой (AspectFill)
Image("myPhoto")
    .resizable()
    .scaledToFill()
    .frame(width: 300, height: 200)
    .clipped() // обрезает выступающие части

Обратите внимание на .clipped() — без него изображение при .scaledToFill() может вылезать за границы .frame().

3. Точное управление соотношением сторон

Иногда нужно задать конкретное соотношение, например 16:9, независимо от размера контейнера:

Image("myPhoto")
    .resizable()
    .aspectRatio(16/9, contentMode: .fit)
    .frame(width: 300)

В этом случае высота будет рассчитана автоматически как 300 * 9 / 16 = 168.75.

4. Работа с GeometryReader для адаптивного размера

Если размер должен зависеть от экрана или родительского контейнера, используйте GeometryReader:

GeometryReader { geo in
    Image("myPhoto")
        .resizable()
        .scaledToFit()
        .frame(width: geo.size.width * 0.8) // 80% от ширины контейнера
}
.frame(height: 200)

5. Продвинутый пример: округлённое изображение с обрезкой

Image("avatar")
    .resizable()
    .scaledToFill()
    .frame(width: 100, height: 100)
    .clipShape(Circle()) // обрезает по кругу
    .shadow(radius: 5)

Итоговые рекомендации

  • Всегда начинайте с .resizable().
  • Для простых случаев используйте .scaledToFit() или .scaledToFill().
  • Не забывайте про .clipped() при .scaledToFill().
  • Если нужно точное соотношение сторон — .aspectRatio(_:contentMode:).
  • Для динамических размеров — GeometryReader.

Теперь ваше изображение будет выглядеть аккуратно на любом экране!

<<>>

Альтернативный подход: модификатор .frame() с параметром alignment

Иногда нужно не просто масштабировать, а ещё и выровнять изображение внутри контейнера. Например, показать верхнюю часть фотографии или центрировать её. Для этого комбинируйте .frame() с alignment:

Image("myPhoto")
    .resizable()
    .scaledToFill()
    .frame(width: 300, height: 200, alignment: .top) // прижать к верху
    .clipped()

Доступные значения: .center, .top, .bottom, .leading, .trailing и их комбинации (.topLeading и т.д.).

Работа с SF Symbols

Для системных иконок (SF Symbols) размер задаётся по-другому — через .font() или .imageScale():

Image(systemName: "star.fill")
    .font(.system(size: 40)) // размер иконки
    .foregroundColor(.yellow)

// Или через imageScale:
Image(systemName: "heart")
    .imageScale(.large) // .small, .medium, .large

Использование AsyncImage для загрузки из сети

Если изображение загружается из интернета, размер тоже настраивается стандартно:

AsyncImage(url: URL(string: "https://example.com/photo.jpg")) { phase in
    if let image = phase.image {
        image
            .resizable()
            .scaledToFit()
            .frame(width: 300)
    } else if phase.error != nil {
        Color.red
    } else {
        ProgressView()
    }
}

Главное — не забыть применить .resizable() к полученному image внутри замыкания.

Этот подход особенно удобен, когда нужно гибко управлять положением изображения внутри контейнера или работать с системными иконками.