Spec-Zone.ru › Swift

Создание приложения для iOS с помощью SwiftUI

Исходный код этого руководства можно найти на GitHub

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

Для начала вам необходимо скачать Xcode из Mac App Store. Оно бесплатное и включает в себя Swift и все остальные инструменты, необходимые для выполнения этого руководства.

Запустите Xcode после установки, а затем выберите «Создать новый проект Xcode». Выберите вкладку iOS вверху, затем выберите шаблон приложения и нажмите Далее.

Подсказка: Хотя мы будем ориентироваться на iOS 16, наш код также отлично будет работать на macOS Ventura и более поздних версиях.

При создании нового проекта Xcode попросит вас указать несколько данных:

  • Для имени продукта введите «WhyNotTry».
  • Для идентификатора организации вы можете ввести com.example. В реальных приложениях мы обычно вводим здесь собственное доменное имя, например org.swift.
  • Убедитесь, что для интерфейса выбран SwiftUI.
  • Вы можете снять флажки с Core Data и Include Tests; мы не будем их использовать здесь.

New Xcode Project

После нажатия Далее Xcode спросит, где вы хотите сохранить проект. Вы можете выбрать любое удобное место, но, возможно, ваш рабочий стол будет наиболее удобным. После этого Xcode создаст новый проект и откроет ContentView.swift для редактирования. Здесь мы будем писать весь наш код, и вы увидите там некоторый код SwiftUI по умолчанию.

Initial SwiftUI project

Пример кода, созданный Xcode для нас, создаёт новый вид под названием ContentView. Виды — это то, как SwiftUI представляет пользовательский интерфейс нашего приложения на экране, и мы можем добавить в них пользовательский макет и логику.

С правой стороны Xcode вы увидите живую предпросмотр этого кода — если вы внесёте изменения в код слева, то они сразу же появятся в предварительном просмотре. Если вы не видите предварительный просмотр, следуйте этим инструкциям, чтобы включить его.

Например, попробуйте заменить код по умолчанию body следующим:

var body: some View {
    Text("Hello, SwiftUI!")
}

Hello SwiftUI

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

Создание статического пользовательского интерфейса

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

Основной частью нашего пользовательского интерфейса будет окружность, отображающая рекомендуемое в данный момент мероприятие. Мы можем нарисовать окружности, просто написав Circle, поэтому замените вид Text("Hello, SwiftUI!") следующим:

Circle()

SwiftUI Circle

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

Обе эти задачи можно выполнить, вызвав методы в представлении Circle. В SwiftUI мы называем их модификаторами представления, поскольку они изменяют внешний вид или работу круга, и в этом случае нам нужно использовать модификатор fill() для окраски круга, а затем модификатор padding(), чтобы добавить немного свободного места вокруг него, как показано ниже:

Circle()
    .fill(.blue)
    .padding()

SwiftUI Circle with Color and Padding

Цвет .blue — это один из нескольких встроенных вариантов, таких как .red, .white и .green. Все они учитывают тему оформления, что означает, что они будут выглядеть немного по-разному в зависимости от того, включена ли на устройстве тёмная тема или светлая.

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

Здесь же нам нужно что-то простое и понятное: мы хотим поместить только один значок поверх круга. Это означает использование ещё одного модификатора под названием overlay(), который располагает одно представление поверх другого. Измените свой код на следующий:

Circle()
    .fill(.blue)
    .padding()
    .overlay(
        Image(systemName: "figure.archery")
    )

SwiftUI Circle with Icon

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

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

Измените свой код Image на следующий:

Image(systemName: "figure.archery")
    .font(.system(size: 144))
    .foregroundColor(.white)

SwiftUI Circle with Icon Sized

Подсказка: Модификатор font() запрашивает шрифт размером 144 пункта, что хорошо отображается на всех устройствах.

Теперь должно выглядеть гораздо лучше.

Далее, добавим немного текста под изображением, чтобы пользователю было ясно, что предлагается. Вы уже знакомы с представлением Text и модификатором font(), поэтому вы можете добавить этот код под кодом Circle:

Text("Archery!")
    .font(.title)

Вместо использования фиксированного размера шрифта, он использует один из встроенных размеров динамического шрифта SwiftUI под названием .title. Это означает, что размер шрифта будет увеличиваться или уменьшаться в зависимости от настроек пользователя, что обычно является хорошей идеей.

Если всё прошло по плану, ваш код должен выглядеть так:

var body: some View {
    Circle()
        .fill(.blue)
        .padding()
        .overlay(
            Image(systemName: "figure.archery")
                .font(.system(size: 144))
                .foregroundColor(.white)
        )

    Text("Archery!")
        .font(.title)
}

Circle With Title Text

Однако то, что вы видите в предварительном просмотре Xcode, вероятно, не соответствует вашим ожиданиям: вы увидите тот же значок, что и раньше, но без текста. Что случилось?

Проблема здесь в том, что мы сказали SwiftUI, что наш пользовательский интерфейс будет содержать два представления — круг и текст — но мы не сказали ему, как их расположить. Хотим ли мы их рядом? Один над другим? Или в другом формате компоновки?

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

VStack {
    Circle()
        .fill(.blue)
        .padding()
        .overlay(
            Image(systemName: "figure.archery")
                .font(.system(size: 144))
                .foregroundColor(.white)
        )

    Text("Archery!")
        .font(.title)
}

И теперь вы должны увидеть ожидаемую ранее компоновку: наш значок лука над текстом «Стрельба из лука!».

Circle With Title Text in a VStack

Это гораздо лучше!

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

К счастью, SwiftUI позволяет нам свободно вкладывать стеки, что означает, что мы можем поместить VStack внутрь другого VStack, чтобы получить желаемое поведение. Итак, измените свой код следующим образом:

VStack {
    Text("Why not try…")
        .font(.largeTitle.bold())

    VStack {
        Circle()
            .fill(.blue)
            .padding()
            .overlay(
                Image(systemName: "figure.archery")
                    .font(.system(size: 144))
                    .foregroundColor(.white)
            )

        Text("Archery!")
            .font(.title)
    }
}

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

Why Not Try Title Added

Теперь у нас есть два VStack представления: внутреннее, содержащее круг и текст «Стрельба из лука!», и внешнее, добавляющее заголовок вокруг внутреннего VStack. Это будет очень полезно позже, когда мы добавим анимацию!

Оживление интерфейса

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

SF Symbols содержит множество интересных мероприятий, поэтому я выбрал несколько, которые хорошо подходят здесь. Наш ContentView структуре уже есть свойство body, содержащее наш код SwiftUI, но мы хотим добавить новые свойства за пределами этого. Итак, измените свой код следующим образом:

struct ContentView: View {
    var activities = ["Archery", "Baseball", "Basketball", "Bowling", "Boxing", "Cricket", "Curling", "Fencing", "Golf", "Hiking", "Lacrosse", "Rugby", "Squash"]

    var selected = "Archery"

    var body: some View {
        // ...
    }
}

Важно: Обратите внимание, как свойства activities и selected находятся внутри структуры — это означает, что они принадлежат ContentView, а не являются просто свободными переменными в нашей программе.

Это создаёт массив различных названий мероприятий и выбирает стрельбу из лука по умолчанию. Теперь мы можем использовать выбранное мероприятие в нашем пользовательском интерфейсе с помощью интерполяции строк — мы можем поместить переменную selected непосредственно в строки.

Для имени мероприятия это просто:

Text("\(selected)!")
    .font(.title)

Для изображения это немного сложнее, потому что нам нужно префикс figure., а затем перевести имя мероприятия в нижний регистр — мы хотим figure.archery, а не figure.Archery, в противном случае символ SF не будет загружен.

Итак, измените свой код Image на следующий:

Image(systemName: "figure.\(selected.lowercased())")

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

var selected = "Baseball"

Showing Baseball

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

Добавьте этот код сейчас:

Button("Try again") {
    // change activity
}
.buttonStyle(.borderedProminent)

Try Again Button

Итак, ваша структура должна быть такой:

VStack {
    // "Why not try…" text

    // Inner VStack with icon and activity name

    // New button code
}

Новый код кнопки выполняет три действия:

  1. Мы создаём Button, передавая заголовок для отображения в качестве метки кнопки.
  2. Комментарий // change activity — это код, который будет выполняться при нажатии на кнопку.
  3. Модификатор buttonStyle() сообщает SwiftUI, что мы хотим, чтобы эта кнопка выделялась, поэтому вы увидите её в синем прямоугольнике с белым текстом.

Простая надпись в качестве действия кнопки не очень интересно — на самом деле, мы хотим установить selected на случайный элемент из массива activities. Мы можем выбрать случайный элемент из массива, вызвав метод с понятным названием randomElement(), поэтому замените комментарий на это:

selected = activities.randomElement()

Этот код выглядит правильно, но на самом деле вызовет ошибки компиляции. Мы говорим Swift, чтобы он выбрал случайный элемент из массива и поместил его в свойство selected, но нет способа убедиться, что в этом массиве что-то есть — он может быть пустым, в этом случае нет случайного элемента для возврата.

Random Element Error

Swift называет это необязательными: randomElement() не вернёт обычную строку, он вернёт необязательную строку. Это означает, что строки может и не быть, поэтому небезопасно присваивать её свойству selected.

Несмотря на то, что мы знаем, что массив никогда не будет пустым — в нём всегда будут активности — мы можем дать Swift разумное значение по умолчанию, чтобы использовать его на случай, если массив окажется пустым в будущем, как это:

selected = activities.randomElement() ?? "Archery"

Это частично исправляет наш код, но Xcode по-прежнему будет отображать ошибку. Проблема сейчас в том, что SwiftUI не нравится, когда мы изменяем состояние нашей программы прямо внутри наших структур представления без предупреждения — он хочет, чтобы мы помечали всё изменяемое состояние заранее, чтобы он знал, как следить за изменениями.

Non-@State mutating

Это делается путём написания @State перед любыми свойствами представления, которые изменятся, например, так:

@State var selected = "Baseball"

Это называется обёрткой свойства, означающей, что она обертывает наше свойство selected с дополнительной логикой. Обертка свойства @State позволяет нам свободно изменять состояние представления, но также автоматически отслеживает изменения своего свойства, чтобы убедиться, что пользовательский интерфейс остаётся в курсе последних значений.

Это исправляет две ошибки в нашем коде, поэтому вы можете нажать Cmd+R, чтобы собрать и запустить ваше приложение в симуляторе iOS. По умолчанию будет предложено бейсбол, но каждый раз при нажатии на «Попробовать ещё» вы увидите изменение.

Running The App in the Simulator

Добавление некоторых штрихов

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

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

Это означает изменение свойства selected следующим образом:

@State private var selected = "Baseball"

Во-вторых, вместо того, чтобы всегда показывать синий фон, мы можем каждый раз выбирать случайный цвет. Это занимает два шага, начиная с нового свойства всех цветов, которые мы хотим выбрать — поместите его рядом со свойством activities:

var colors: [Color] = [.blue, .cyan, .gray, .green, .indigo, .mint, .orange, .pink, .purple, .red]

Теперь мы можем изменить модификатор fill() круга на использование randomElement() в этом массиве или .blue, если по какой-то причине массив окажется пустым:

Circle()
    .fill(colors.randomElement() ?? .blue)

В-третьих, мы можем разделить активность VStack и кнопку «Попробовать ещё», добавив новое представление SwiftUI между ними, называемое Spacer. Это гибкое пространство, которое автоматически расширяется, что означает, что оно подтолкнёт значок активности к верху экрана, а кнопку — к низу.

Вставьте его между ними, как это:

VStack {
    // current Circle/Text code
}

Spacer()

Button("Try again") {
    // ...
}

Если вы добавите несколько разделителей, они равномерно разделят пространство между ними. Если вы попытаетесь поместить второй разделитель перед текстом «Почему бы не попробовать…», вы увидите, что я имею в виду — SwiftUI создаст равное пространство над текстом и под именем активности.

View With Spacers

И, наконец, было бы неплохо, если бы переход между активностями был плавнее, что мы можем сделать, анимировав изменение. В SwiftUI это делается путём обертывания изменений, которые мы хотим анимировать, вызовом функции withAnimation(), например так:

Button("Try again") {
    withAnimation {
        selected = activities.randomElement() ?? "Archery"
    }
}
.buttonStyle(.borderedProminent)

Это заставит нашу кнопку переключаться между активностями с плавным затуханием. Если вы хотите, вы можете настроить анимацию, передав желаемую анимацию вызову withAnimation(), как это:

withAnimation(.easeInOut(duration: 1)) {
    // ...
}

Это улучшение, но мы можем сделать лучше!

Затухание происходит потому, что SwiftUI видит, как изменяются цвет фона, значок и текст, поэтому он удаляет старые представления и заменяет их новыми. Ранее я заставил вас создать внутреннее VStack для размещения этих трёх представлений, и теперь вы понимаете почему: мы собираемся сказать SwiftUI, что эти представления могут быть идентифицированы как единая группа, и что идентификатор группы может меняться со временем.

Для этого нам нужно начать с определения дополнительного состояния программы внутри нашего представления. Это будет идентификатор нашего внутреннего VStack, и поскольку оно будет меняться по мере работы нашей программы, мы будем использовать @State. Добавьте это свойство рядом со свойством selected:

@State private var id = 1

Подсказка: Это больше локального состояния представления, поэтому хорошей практикой является пометка его private.

Далее, мы можем сказать SwiftUI изменить этот идентификатор каждый раз, когда нажимается наша кнопка, например так:

Button("Try again") {
    withAnimation(.easeInOut(duration: 1)) {
        selected = activities.randomElement() ?? "Archery"
        id += 1
    }
}
.buttonStyle(.borderedProminent)

Наконец, мы можем использовать модификатор SwiftUI id() для прикрепления этого идентификатора ко всему внутреннему представлению VStack, что означает, что при изменении идентификатора SwiftUI должен рассматривать всё внутреннее представление VStack как новое. Это заставит его анимировать удаление старого VStack и добавление нового VStack, а не только отдельных представлений внутри него. Ещё лучше, мы можем контролировать, как происходит переход добавления и удаления, используя модификатор transition(), который имеет различные встроенные переходы, которые мы можем использовать.

Итак, добавьте эти два модификатора во внутреннее представление VStack, сообщив SwiftUI идентифицировать всю группу с использованием нашего свойства id и анимировать переходы добавления и удаления с помощью сдвига:

.transition(.slide)
.id(id)

Нажмите Cmd+R, чтобы ещё раз запустить приложение, и вы увидите, что нажатие на «Попробовать ещё» теперь плавно анимирует удаление старой активности с экрана и заменяет её новой. Оно даже перекрывает анимации, если вы нажимаете «Попробовать ещё» многократно!

Куда дальше?

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

Если вы хотите продолжить изучение SwiftUI, доступно много бесплатных ресурсов. Например, Apple публикует множество учебников, охватывающих важные темы, рисование и анимацию, дизайн приложений и многое другое. Мы также опубликуем ссылки на другие популярные учебники на Swift.org — мы большая и дружелюбная община, и мы рады приветствовать вас!

Исходный код этого руководства можно найти на GitHub

The Swift Programming Language, Copyright © 2014-2025 Apple Inc.
Swift and the Swift logo are trademarks of Apple Inc.

Documentation for Swift 6.0.3


https://www.swift.org/getting-started/swiftui

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API