Как стать автором
Обновить

SwiftUI уроки (часть 2)

Время на прочтение12 мин
Количество просмотров3.8K

ссылка на 1ю часть

Если вы уже ознакомились с первой частью, то у вас есть базовое понимание что из себя представляет SUI и знаете как отобразить текстовый контент, в этой части давайте познакомимся с элементом отображающим изображения.

В дополнении к Text, Image это еще один базовый элемент который вы будете использовать в своей IOS разработке. SUI представляет View которое так и называется Image, эта вью рендерит и отрисовывает изображения на экран устройства. Также как и в прошлой части мы разберемся как использовать Image и что с ним можно сделать на специальном маленьком демо приложении.

Что мы собираемся изучить в ходе этого урока:

  • Что такое SF Symbols и как отображать системные изображения

  • Как отображать свои собственные изображения

  • Как менять размеры Image

  • Как отображать Image на весь экран используя ignoresSafeArea

  • Как изменить форму изображения и сделать его круглым

  • Как добавить overlay к изображению

Создаем новый проект для работы с Image

Для начала запустите Xcode и создайте новый проект используя шаблон App также как и в первой части уроков. Назовите проект SwiftUIImage, под именем организации вы можете указать либо имя организации либо как в нашем случае com.swiftexplorer. Разумеется убедитесь, что вы выбрали interface SwiftUI, после этих настроек кликайте дальше и выберите папку для вашего проекта

Когда вы сохраните проект, Xcode должен загрузить ContentView.swift файл и отобразить дизайн/превью канвас

Узнаем SF Symbols

Прежде чем мы начнем отображать изображения на экране давайте разберемся откуда вообще мы будем брать эти самые изображения, абсолютно понятно что мы можем использовать свои собственные изображения полученные от дизайнеров или скачанные из других источников, однако начиная с IOS 13 Apple предоставили целый набор различных картинок, которые похожи на иконки и многие из них вы можете видеть в самой операционной системе в IOS, MacOS и прочих Apple операционках. На момент написания статьи Apple выпустили SF Symbols 5 который уже содержит около 5000 различных иконок, некоторые из них поддерживают анимацию и возможность использовать несколько цветов непосредственно в самой иконке вместо одного как это было раньше.

Важно понимать какие иконки вы сможете использовать в зависимости от максимальной поддерживаемой вами операционной системы.

SF Symbols v1.0 доступно в iOS/iPadOS/tvOS/Mac Catalyst 13.0, watchOS 6.0 and macOS 11.0

SF Symbols v2.0 доступно в iOS/iPadOS/tvOS/Mac Catalyst 14.0, watchOS 7.0 and macOS 11.0

SF Symbols v3.0 доступно в iOS/iPadOS/tvOS/Mac Catalyst 15.0, watchOS 8.0 and macOS 12.0

SF Symbols v4.0 доступно в iOS/iPadOS/tvOS/Mac Catalyst 16.0, watchOS 9.0 and macOS 13.0

SF Symbols v5.0 доступно в iOS/iPadOS/tvOS/Mac Catalyst 17.0, watchOS 10.0 and macOS 14.0

Чтобы начать использовать SF Symbols все что нужно это знать название этого самого символа, Apple выпустили специальное приложение со всеми символами, чтобы вы легко могли использовать их в работе и находить нужные с помощью поиска или контекстного меню.

Найти это приложение можно здесь https://developer.apple.com/sf-symbols/

Будет очень хорошо если вы скачаете его до того как продолжите читать эту статью.

Отображаем System Image

Для того чтобы отобразить системное изображение на экране, все что нам требуется это инициализировать Image с параметром systemName:

struct ContentView: View {
    var body: some View {
        VStack {
            Image(systemName: "sun.max.circle.fill")
        }
        .padding()
    }
}

Этот фрагмент кода создаст image view и загрузит специальное системное изображение. SF Symbols интегрирован с San Francisco шрифтом, а это значит что можно использовать модификаторы связанные с шрифтами, например:

struct ContentView: View {
    var body: some View {
        VStack {
            Image(systemName: "sun.max.circle.fill")
    .font(.system(size: 200))
        }
        .padding()
    }
}

Таким образом с помощью этого модификатора и его параметра мы можем четко установить размер нашего изображения.

Также, так как наше изображение это фактически шрифт мы можем применить к нему и модификатор foregroundColor с которым мы познакомились в предыдущей части, чтобы изменить его цвет, давайте например попробуем поменять цвет на оранжевый:

struct ContentView: View {
    var body: some View {
        VStack {
            Image(systemName: "sun.max.circle.fill")
    .font(.system(size: 200))
    .foregroundColor(.orange)
        }
        .padding()
    }
}

Точно также как и с обычным шрифтом мы можем использовать и модификатор добавляющий тень shadow

struct ContentView: View {
    var body: some View {
        VStack {
            Image(systemName: "sun.max.circle.fill")
    .font(.system(size: 200))
    .foregroundColor(.orange)
    .shadow(color: .black, radius: 10, x: 0, y: 20)
        }
        .padding()
    }
}

Используем собственные изображения (не системные)

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

В целом вы можете использовать любое изображение которое скачаете из интернета, но если хотите следовать шаг за шагом со мной, то вот ссылка на изображение. https://clck.ru/393pdM

После того как скачаете это изображение поменяйте имя файла на night.jpg чтобы дальше было легче следовать статье.

Прежде чем вы сможете начать использовать это изображение в своем проекте, первый шаг который вам нужно сделать, это добавить его в каталог ассетов

Кликните по Assets и перетащите картинку как указано на скриншоте.

Если вы только изучаете разработку приложений для iOS нужно оговориться что такое Assets каталог. В этом каталоге ресурсов вы храните ресурсы приложений, такие как изображения, цвет и данные. После того, как вы поместите изображение в каталог активов, вы можете загрузить изображение в то вью которое поддерживает отрисовку картинок просто ссылаясь на его название. Кроме того, вы можете настроить, на какое устройство ту или картинку можно загрузить (например, только iPhone или как в нашем случае universal т.е. везде — как это выставляется по умолчанию).

Наконец, мы готовы отобразить эту картинку на экране, сделать это можно следующим образом:

import SwiftUI

struct ContentView: View {
    var body: some View {
        VStack {
            Image("night")
        }
        .padding()
    }
}

#Preview {
    ContentView()
}

Поздравляю вы добавили свое первое кастомное изображение в приложение и сделать это можно просто передав в инициализатор нашего Image название файла содержащего картинку.

Как вы видите картинка отображается на экране заполняя весь экран и соответственно некоторые ее части обрезаны, это связано с размерами изображения, если вы скачали такую же картинку, то ее размеры 2000 на 1000 с чем и связано такое довольно странное отображение.

Давайте все же попробуем поместить всю картинку на наш экран чтобы она не обрезалась, для этого достаточно использовать модификатор resizable, по умолчанию он вписывает всю картинку в экран и применяет к ней режим scale to fill. Это означает, что исходное изображение будет масштабироваться так — чтобы заполнить весь экран.

struct ContentView: View {
    var body: some View {
        VStack {
            Image("night")
    .resizable()
        }
        .padding()
    }
}

#Preview {
    ContentView()
}

Теперь изображение заполняет всю safe area, как определено операционной системой. Концепция safe area зоны существует уже довольно давно. Безопасная область определяется как область просмотра, в которой можно безопасно показать наши компоненты пользовательского интерфейса. Например, как вы можете видеть на скриншоте, безопасная область — это область просмотра, которая исключает верхнюю полосу (т.е. строку состояния) и нижнюю полосу. Безопасная область предотвратит случайное скрытие компонентов пользовательского интерфейса системы, таких как строка состояния, панель навигации и панель вкладок (status bar, navigation bar, tab bar).

Но если вы хотите отобразить свою картинку например на весь экран вы можете просто игнорировать безопасную зону установив модификатор ignoresSafeArea.

Обратите внимание что я избавился и от padding() поиграйте с этим значением и попробуйте понять почему.

struct ContentView: View {
    var body: some View {
        VStack {
            Image("night")
    .resizable()
    .ignoresSafeArea()
        }
    }
}

#Preview {
    ContentView()
}

Разумеется вы можете использовать этот модификатор таким образом чтобы игнорировать безопасную зону только в определенных зонах, например вы хотите оставить безопасную зону в верхней части вашего экрана — возможно вы хотите чтобы статус бар и навигейшн бар всегда были видны. Тогда вы можете указать параметры модификатора таким образом:

struct ContentView: View {
    var body: some View {
        VStack {
            Image("night")
    .resizable()
    .ignoresSafeArea(.container, edges: .bottom)
        }
    }
}

#Preview {
    ContentView()
}

Aspect Fit и Aspect Fill

Если вы посмотрите на оба изображения в предыдущем разделе и сравните их с оригинальным изображением, вы обнаружите, что соотношение сторон искажено. Режим scaledToFill не учитывает соотношение сторон исходного изображения. Он растягивается с каждой стороны, чтобы соответствовать области обзора. Чтобы сохранить исходное соотношение сторон, вы можете применить модификатор scaledToFit следующим образом:

struct ContentView: View {
    var body: some View {
        VStack {
            Image("night")
    .resizable()
    .scaledToFit()
        }
    }
}

Кстати этого можно добиться и другим образом, можно использовать модификатор aspectRatio а его режим отображения контента уже выставить как .fit

struct ContentView: View {
    var body: some View {
        VStack {
            Image("night")
    .resizable()
    .aspectRatio(contentMode: .fit)
        }
    }
}

В некоторых случаях вы можете сохранить соотношение сторон изображения, но растянуть изображение как можно больше, для этого примените режим отображения контента в .fill:

struct ContentView: View {
    var body: some View {
        VStack {
            Image("night")
    .resizable()
    .aspectRatio(contentMode: .fill)
        }
    }
}

Чтобы лучше понять разницу между этими двумя режимами, давайте ограничим размер изображения. Модификатор frame позволяет контролировать размер представления.

Давайте установим ширину фрейма на 200 поинтов, соответсвенно и ширина изображения будет ограничена 200 поинтами

struct ContentView: View {
    var body: some View {
        VStack {
            Image("night")
    .resizable()
    .aspectRatio(contentMode: .fit)
    .frame(width: 200)
        }
    }
}

А теперь давайте поменяем contentMode на .fill

struct ContentView: View {
    var body: some View {
        VStack {
            Image("night")
    .resizable()
    .aspectRatio(contentMode: .fill)
    .frame(width: 200)
        }
    }
}

Изображение будет масштабировано по размеру, но исходное соотношение сторон будет сохранено.

После изменения контент режима на .fill картинка очень похоже что выглядит так как будто мы оставим просто resizable(). Однако если поменять режим отображения на Selectable в нашем канвасе то вы увидите что соотношение сторон именно такое как мы и указали.

Напоминаю что под Selectable в Canvas имеется ввиду это:

Одна вещь, которую вы можете заметить, это то, что визуально ширина изображения все еще занимает всю ширину экрана. Чтобы ширина отображалась в соответствии с заданными параметрами, можно использовать модификатор clipped для устранения лишних частей view (левый и правый края в нашем случае).

struct ContentView: View {
    var body: some View {
        VStack {
            Image("night")
    .resizable()
    .aspectRatio(contentMode: .fill)
    .frame(width: 200)
    .clipped()
        }
    }
}

Создаем круглое изображение

В дополнение к таким модификатором как clipping у нас есть и другие обрезчики, которые в том числе могут задать определеную форму (shape), SUI предоставляет такой модификатор как clipShape, который можно использовать с разными формами, давайте посмотрим на примере:

struct ContentView: View {
    var body: some View {
        VStack {
            Image("night")
    .resizable()
    .clipShape(Circle())
        }
    }
}

С помощью структуры Circle и протокола Shape который она реализует мы получили обрезку нашего изображения по кругу, на самом деле таких шейпов которые представляет нам Apple по умолчанию несколько, хотя вы сможете позже использовать и свои собственные.

Вот еще один пример того что предоставляет Apple по умолчанию:

struct ContentView: View {
    var body: some View {
        VStack {
            Image("night")
    .resizable()
    .clipShape(Ellipse())
        }
    }
}

Не вдавайтесь сильно в подробности так как пока эти знания вам не нужны, но все равно посмотрите как можно сделать свой собственный кастомный шейп и затем его применить:

/// Сам Shape Который будем использовать для обрезки контента в форме звезды

struct Star: Shape {
 let corners: Int
 let innerRatio: CGFloat
 
 init(corners: Int = 5, innerRatio: CGFloat = 0.5) {
  self.corners = corners
  self.innerRatio = innerRatio
 }
 
 func path(in rect: CGRect) -> Path {
  guard corners >= 2 else { return Path() }
  
  let center = CGPoint(x: rect.width / 2, y: rect.height / 2)
  let outerRadius = min(rect.width, rect.height) / 2
  let innerRadius = outerRadius * innerRatio
  
  var path = Path()
  
  for i in 0..<corners * 2 {
   let angle = CGFloat.pi * CGFloat(i) / CGFloat(corners)
   let radius = (i % 2 == 0) ? outerRadius : innerRadius
   
   let x = center.x + radius * cos(angle)
   let y = center.y + radius * sin(angle)
   
   if i == 0 {
    path.move(to: CGPoint(x: x, y: y))
   } else {
    path.addLine(to: CGPoint(x: x, y: y))
   }
  }
  
  path.closeSubpath()
  
  return path
 }
}

Измененный ContentView

struct ContentView: View {
    var body: some View {
        VStack {
            Image("night")
    .resizable()
    .clipShape(Star())
        }
    }
}

Результат:

Настройка прозрачности

В SUI есть модификатор именуемый opacity с помощью которого можно контролировать то насколько прозрачна ваша вью. В этот модификатор можно передать значение от 0 до 1 который и будет индицировать то насколько прозрачным будет вью. Если передать 0 то изображение будет полностью прозрачным, если же передать 1 то оно будет абсолютно не прозрачным.

Давайте попробуем передать внутрь модификатора значение равное 0.5, что будет означать что наша вью будет прозрачна на 50%

struct ContentView: View {
    var body: some View {
        VStack {
            Image("night")
    .resizable()
    .clipShape(Circle())
    .opacity(0.5)
        }
    }
}

Чтобы убедиться в том что наша вью прозрачная давайте положим ее в ZStack который позволяет добавлять вьюшки по оси Z (не в давайтесь пока сильно в подробности, мы это еще успеем разобрать позже), положим вниз круг покрашенный в красный цвет, а нашего положим нашу картинку которую сделаем чуть меньше по размеру. Можно заметить что картинка и сама как будто покрасилась в красный цвет.

struct ContentView: View {
    var body: some View {
  ZStack {
   Circle()
    .foregroundStyle(.red)
   VStack {
    Image("night")
     .resizable()
     .frame(width: 200)
     .clipShape(Circle())
     .opacity(0.5)
   }

  }
    }
}

Обратите внимание как изменится окраска картинки в случае если мы уберем модификатор opacity, а соответственно изображение станет полностью не прозрачным.

struct ContentView: View {
    var body: some View {
  ZStack {
   Circle()
    .foregroundStyle(.red)
   VStack {
    Image("night")
     .resizable()
     .frame(width: 200)
     .clipShape(Circle())
//     .opacity(0.5)
   }

  }
    }
}

C помощью модификатора прозрачности можно добиться некоторых очень красивых визуальных эффектов, отдайте эту мысль на подумать вашему дизайнеру.

Применение наложения (overlay) к изображению

Когда вы собираете ваше приложение, иногда может возникнуть ситуация когда вы бы хотели положить тот или иной объект поверх другой вашей вью. Это мы сможем сделать и с нашей Image с помощью модификатора overlay, давайте создадим эффект как будто кто-то поставил лайк на нашу картинку.

struct ContentView: View {
 var body: some View {
  ZStack {
   Circle()
    .foregroundStyle(.red)
   VStack {
    Image("night")
     .resizable()
     .frame(width: 200)
     .clipShape(Circle())
     .overlay {
      Image(systemName: "heart.fill")
       .font(.system(size: 50))
       .foregroundStyle(.red)
       .opacity(0.8)
     }
   }
  }
 }
}

Модификатор overlay под капотом имеет два параметра, один из них отвечает за то как именно должен отображаться контент alignment — по дефолту он всегда отображается по центру, а второй это замыкание которое возвращает вью. Так что на самом деле вы можете положить в оверлей не только другие картинки, но и вообще любой другой компонент который является View. Например:

struct ContentView: View {
 var body: some View {
  ZStack {
   Circle()
    .foregroundStyle(.red)
   VStack {
    Image("night")
     .resizable()
     .frame(width: 200)
     .clipShape(Circle())
     .overlay {
      Text("Night City")
       .foregroundStyle(.white)
       .font(.system(.title))
       .fontWeight(.bold)
     }
   }
  }
 }
}

Как вы видите мы добавили текст поверх нашей картинки и на самом деле как я уже говорил ранее вы можете добавить в оверлей абсолютно любую вью и настроить так как вам нужно с помощью модификаторов которые будут относиться к этой вашей новой View.

Затемнить изображение с помощью наложения

Мы можем не только просто добавлять какие то другие элементы на наше изображение с помощью модификатора overlay, но мы можем и получать различные визуальные эффекты, давайте например затемним наш и без того темный ночной город.

struct ContentView: View {
 var body: some View {
  ZStack {
   Circle()
    .foregroundStyle(.red)
   VStack {
    Image("night")
     .resizable()
     .frame(width: 200)
     .clipShape(Circle())
     .overlay {
      Circle()
       .opacity(0.6)
     }
   }
  }
 }
}

Итак, мы добавили в оверлей еще один круг, он по размеру такой же как и сам наш основной круг в котором находится изображение, по умолчанию этот круг черный, но мы добавили ему модификатор прозрачности и указали 0.6 степени прозрачности, за счет этого мы получили еще более темный город, но что если я вам скажу что даже у этого оверлея может быть свой собственный оверлей? Давайте это проверим:

struct ContentView: View {
 var body: some View {
  ZStack {
   Circle()
    .foregroundStyle(.red)
   VStack {
    Image("night")
     .resizable()
     .frame(width: 200)
     .clipShape(Circle())
     .overlay {
      Circle()
       .opacity(0.6)
       .overlay {
        Text("Night City")
         .foregroundStyle(.white)
         .font(.system(.title))
         .fontWeight(.bold)
       }
     }
   }
  }
 }
}

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

Применение многоцветных параметров к SF Symbols

Начиная с iOS 15, SF Symbols предоставляет четыре режима рендеринга, которые позволяют использовать несколько опций при применении цвета к символам. В зависимости от выбранного вами режима вы можете применить один цвет или многоцветные к системной картинке. Например, thermometer.sun.fill — это символ, который поддерживает рендеринг палитры. Вы можете применить два или более контрастных цвета к символу. Взгляните на скриншот как можно проверить отображение цвета с помощью приложения SF Symbols

Давайте попробуем отобразить такую картинку в нашем приложении:

struct ContentView: View {
 var body: some View {
  VStack {
   Image(systemName: "thermometer.sun.fill")
    .font(.system(size: 80))
    .symbolRenderingMode(.palette)
    .foregroundStyle(.red, .yellow, .blue)
  }
 }
}

На 15 строке мы установили как именно мы хотим чтобы рендерилась наша картинка, если мы хотим иметь возможность контролировать цвета то нам нужен режим палетки, а затем на 16 строке с помощью foregroundStyle один из инициализаторов которого принимает три цвета назначили цвет каждому из элементов нашей картинки. Еще раз важное замечание, эти модификаторы работают только с SF Symbols которые поддерживают данный вид рендера, поэтому для начала проверяйте в приложении SF Symbols возможно ли так раскрасить взятую вами иконку.

Переменные цвета

В iOS 16 SF Symbols добавляет новую функцию под названием Variable Color. Вы можете изменить цвет символа, изменив процентное значение. Это особенно полезно, когда вы используете некоторые символы для обозначения прогресса. Поиграть с этим значением можно в разделе изменяемый цвет в приложении SF Symbols

Переменный цвет можно использовать с каждым режимом рендеринга, доступным для символов SF. Вы можете перейти на другие режимы рендеринга, чтобы увидеть все возможные эффекты.

Теперь давайте попробуем применить это в коде:

struct ContentView: View {
 var body: some View {
  VStack {
   Image(systemName: "waveform", variableValue: 0.1)
    .font(.system(size: 80))
    .foregroundStyle(.red)
  }
 }
}

Здесь мы взяли иконку waveform и установили значение в 0.1 как будто какая-то операция готова только на 10 процентов, давайте попробуем передвинуть это значение как будто операция готова на 55%

struct ContentView: View {
 var body: some View {
  VStack {
   Image(systemName: "waveform", variableValue: 0.55)
    .font(.system(size: 80))
    .foregroundStyle(.red)
  }
 }
}

Как вы видите цвет картинки заполнился как будто на 55 процентов, можно подумать как это использовать это в приложении, например анимировать это значение от 0 до 100 при загрузке — например в данном случае музыкального файла.

Итоги

В этой части мы поработали с изображениями. В SwiftUI очень легко модифицировать изображения добавляя те или иные параметры или применяя те или иные модификаторы, также вы узнали про overlay.

Если вы инди разработчик приложений, то SF Symbols сэкономят вам много времени и денег на создание иконок!

Как и прежде подписывайтесь на мой телеграм канал — https://t.me/swiftexplorer

В ближайшее время выйдут следующие части уроков по SwiftUI.

Спасибо за прочтение!

Теги:
Хабы:
Всего голосов 4: ↑4 и ↓0+4
Комментарии0

Публикации

Истории

Работа

Swift разработчик
18 вакансий
iOS разработчик
17 вакансий

Ближайшие события

15 – 16 ноября
IT-конференция Merge Skolkovo
Москва
22 – 24 ноября
Хакатон «AgroCode Hack Genetics'24»
Онлайн
28 ноября
Конференция «TechRec: ITHR CAMPUS»
МоскваОнлайн
25 – 26 апреля
IT-конференция Merge Tatarstan 2025
Казань