Connect with us

Разработка

Делаем DVD-заставку на SwiftUI

Как и многие из вас, я помню, как видел эту заставку в детстве, но совершенно забыл о ней, пока недавно не пересматривал эту сцену из «Офиса».

Опубликовано

/

     
     

Помните DVD-заставку из начала 2000-х? Она не только была практичным инструментом для предотвращения выгорания экрана на ЭЛТ-дисплеях, но и стала культурной иконой.

Как и многие из вас, я помню, как видел эту заставку в детстве, но совершенно забыл о ней, пока недавно не пересматривал эту сцену из «Офиса»:

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

Давайте приступим!

Работа с Canvas API

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

struct ContentView: View {
    @State private var position: CGPoint = .zero

    private let canvasSize: CGSize = UIScreen.main.bounds.size
    private let imageSize: CGSize = CGSize(width: 128, height: 76)
    private let image = Image("dvd_logo")

    var body: some View {
        Canvas { [position] context, size in
            // Set the background color to .black
            context.fill(Path(CGRect(origin: .zero, size: size)), with: .color(.black))

            // Draw image at current position
            var image = context.resolve(image)
            image.shading = .color(.red)
            context.draw(
                image,
                in: CGRect(x: position.x, y: position.y, width: imageSize.width, height: imageSize.height)
            )
        }
        .onAppear {
            // Set initial position to the center of the canvas after the view appears
            position = CGPoint(x: (canvasSize.width - imageSize.width) / 2, y: (canvasSize.height - imageSize.height) / 2)
        }
        .ignoresSafeArea()
    }
}

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

Необходимость группы захвата

На этом этапе я столкнулся с интересным поведением API Canvas.

Если я явно не объявил position в группе захвата Canvas, изображение всегда отрисовывается в нулевом положении .zero, даже если position обновляется в onAppear.

Такое поведение становится еще более интересным, если сравнить его с обычным представлением SwiftUI с аналогичной настройкой, где все ведет себя так, как и ожидалось.

В следующем представлении SwiftUI элемент Text точно отражает центр представления, а представление перерисовывается при изменении значения position в onAppear.

struct ContentView: View {
    @State private var position: CGPoint = .zero
    private let canvasSize: CGSize = UIScreen.main.bounds.size

    var body: some View {
        VStack {
            Text("\(position.x), \(position.y)")
        }
        .onAppear {
            // Set initial position to the center of the view after it appears
            position = CGPoint(x: canvasSize.width / 2, y: canvasSize.height / 2)
        }
    }
}

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

Создание цикла рисования

На данный момент мы успешно воспроизвели внешний вид заставки DVD.

Далее нам нужно будет создать механизм, запускающий перерисовку View через регулярные промежутки времени (например, 30 кадров в секунду). Кроме того, нам понадобится способ обновлять положение изображения в каждом из этих интервалов.

Чтобы перерисовывать View с частотой 30 кадров в секунду, мы можем просто использовать комбинацию таймера и onReceive:

private let framesPerSecond: Double = 1 / 30

Canvas { ... }
.onReceive(Timer.publish(every: framesPerSecond, on: .main, in: .common).autoconnect()) { _ in
  // TODO: Update image position here
}

Функция autoconnect() в SwiftUI автоматически подключает издателя (например, Timer) к представлению, позволяя представлению реагировать на значения, выдаваемые издателем, без ручной настройки. Это упрощает процесс создания реактивных пользовательских интерфейсов за счет автоматической обработки логики подписки и обновления.

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

Давайте добавим вектор скорости, чтобы управлять скоростью перемещения логотипа:

@State private var velocity: CGVector = CGVector(dx: 1, dy: 1)

В блоке onReceive мы обновим position.x и position.y, добавив соответствующие значения из velocity. Эта настройка позволяет нам управлять скоростью движения по осям X и Y независимо друг от друга.

.onReceive(Timer.publish(every: framesPerSecond, on: .main, in: .common).autoconnect()) { _ in
    // Update position based on velocity
    self.position.x += self.velocity.dx
    self.position.y += self.velocity.dy
}

Если бы вы запустили код сейчас, то заметили бы, что изображение постепенно уходит за пределы экрана:

Делаем DVD-заставку на SwiftUI

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

Добавление проверок границ

Как мы должны реагировать, когда наше изображение достигает края?

Если изображение приближается к правому краю экрана, то все, что нам нужно сделать, это изменить направление его горизонтального движения, верно? Аналогично, если изображение приближается к верхней части экрана, нам нужно только перевернуть его вертикальное движение, сохранив горизонтальное движение прежним.

С этим предположением проверка границ становится очень простой:

.onReceive(Timer.publish(every: framesPerSecond, on: .main, in: .common).autoconnect()) { _ in
    // Update position based on velocity
    self.position.x += self.velocity.dx
    self.position.y += self.velocity.dy

    // Check if image hits a horizontal edge
    if self.position.x + self.imageSize.width >= canvasSize.width || self.position.x <= 0  {
        // Flip horizontal direction
        self.velocity.dx *= -1
    }
    
    // Check if image hits a vertical edge
    if self.position.y + self.imageSize.height >= canvasSize.height ||  self.position.y <= 0 {
        // Flip vertical direction
        self.velocity.dy *= -1
    }
}

Делаем DVD-заставку на SwiftUI

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

@State private var imageColor: Color = .green

// Canvas
var image = context.resolve(image)
image.shading = .color(imageColor)

// .onReceive
// Check if image hits a horizontal edge
if self.position.x + self.imageSize.width >= canvasSize.width || self.position.x <= 0  {
    // Flip horizontal direction
    self.velocity.dx *= -1
    self.imageColor = Color.random()
}

// Check if image hits a vertical edge
if self.position.y + self.imageSize.height >= canvasSize.height ||  self.position.y <= 0 {
    // Flip vertical direction
    self.velocity.dy *= -1
    self.imageColor = Color.random()
}

extension Color {
    static func random() -> Color {
        let red = Double.random(in: 0...1)
        let green = Double.random(in: 0...1)
        let blue = Double.random(in: 0...1)
        return Color(red: red, green: green, blue: blue)
    }
}

Весь код вы можете найти здесь: https://github.com/aryamansharda/DVDScreensaver

Надеюсь, вам понравилась эта статья! Если понравилось, пожалуйста, поделитесь ею в социальных сетях.

Источник

Если вы нашли опечатку - выделите ее и нажмите Ctrl + Enter! Для связи с нами вы можете использовать info@apptractor.ru.

Наши партнеры:

LEGALBET

Мобильные приложения для ставок на спорт
Telegram

Популярное

Сообщить об опечатке

Текст, который будет отправлен нашим редакторам: