На днях я начал возиться с преобразованием 2D-дизайна в изометрические виды в Figma. Я подумал, что было бы неплохо создать ViewModifier в SwiftUI, который делает то же самое. Разместив скриншот своей работы в твиттере, я решил написать этот туториал.

Обзор

Я разбил руководство на две части (обе ниже):

  1. Применение изометрического преобразования к виду
  2. Экструзия изометрического вида

Прежде чем начать, рассмотрите возможность подписки по этой ссылке, и если вы не читаете ее на TrailingClosure.com, приходите к нам как-нибудь!

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

Преобразование 2D-видов в изометрические виды

Один из самых простых способов трансформировать 2D-вид - повернуть его 45°, а затем масштабировать его высоту с коэффициентом 0.5. Просто поверните и сожмите!

Написано под заказ ViewModifier

Теперь мы можем предпринять шаги, описанные выше, и преобразовать это в собственный SwiftUI ViewModifier следующим образом:

struct IsometricViewModifier: ViewModifier {
    func body(content: Content) -> some View {
        content
            .rotationEffect(Angle(degrees: 45), anchor: .center)
            .scaleEffect(x: 1.0, y: 0.5, anchor: .center)
    }
}

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

Обратный обратный!

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

Экструзия изометрических видов

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

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

Моделирование экструзии

Чтобы создать идеальную экструзию вида, нам нужно взять цвета снаружи (две передние изометрические стороны) и растянуть их вниз вдоль изометрической оси z. Я добиваюсь этого, масштабируя предоставленный контент по оси x или y, а затем обрезая его до нужного размера и формы. Это позволяет нам сохранять точные цвета из представлений, таких как изображения и градиенты.

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

Пользовательский ExtrudeModifier

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

struct ExtrudeModifier<Texture: View> : ViewModifier {
    
    var depth: CGFloat // Extrusion Depth
    var texture: Texture
    
    func body(content: Content) -> some View {
        content
            // Front Left Side
            .overlay(
            	// Content (Texture) here
            , alignment: .center)
            
            // Front Right Side
            .overlay(
                // Content (Texture) here
            , alignment: .center)
    }
}

Ссылка на шаги в коде ниже

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

Примечание. На фотографиях выше уже применен offset(x: 0, geo.size.height). Я сделал это, чтобы помочь читателю визуализировать шаги, которые я выполнял в коде.

struct ExtrudeModifier<Texture: View> : ViewModifier {
    
    var depth: CGFloat
    var texture: Texture
    
    func body(content: Content) -> some View {
        content
            // Front Left Side
            .overlay(
                GeometryReader { geo in
                    texture // Step 2
                        .brightness(-0.05)
                        .scaleEffect(x: 1, y: geo.size.height * geo.size.height, anchor: .bottom) // Step 3
                        .frame(height: depth, alignment: .top) // Step 4
                        .mask(Rectangle())
                        .rotation3DEffect(
                            Angle(degrees: 180),
                            axis: (x: 1.0, y: 0.0, z: 0.0),
                            anchor: .center,
                            anchorZ: 0.0,
                            perspective: 1.0
                        )
                        .projectionEffect(ProjectionTransform(CGAffineTransform(a: 1, b: 0, c: 1, d: 1, tx: 0, ty: 0))) // Step 5
                        .offset(x: 0, y: geo.size.height)
                        
                }
                , alignment: .center)
            
            // Front Right Side
            .overlay(
            	// TO DO    
            , alignment: .center)
                
    }
}

Теперь другая сторона
Теперь я применим ту же технику к другой стороне изометрического вида.

struct ExtrudeModifier<Texture: View> : ViewModifier {
    
    var depth: CGFloat
    var texture: Texture
    
    func body(content: Content) -> some View {
        content
            // Front Left Side
            .overlay(
           		// See code from before...
            , alignment: .center)
            
            // Front Right Side
            .overlay(
                GeometryReader { geo in
                    texture
                        .brightness(-0.1)
                        .scaleEffect(x: geo.size.width * geo.size.width, y: 1.0, anchor: .trailing)
                        .frame(width: depth, alignment: .leading)
                        .clipped()
                        .rotation3DEffect(
                            Angle(degrees: 180),
                            axis: (x: 0.0, y: 1.0, z: 0.0),
                            anchor: .leading,
                            anchorZ: 0.0,
                            perspective: 1.0
                        )
                        .projectionEffect(ProjectionTransform(CGAffineTransform(a: 1, b: 1, c: 0, d: 1, tx: 0, ty: 0)))
                        .offset(x: geo.size.width + depth, y: 0 + depth)
                }
                , alignment: .center)
                
    }
}

Почему вы изменили яркость сторон?

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

Собираем все вместе

При желании вы можете создать собственный IsometricView компонент, в котором используются ExtrudeModifier и IsometricViewModifier, указанные выше. Этот настраиваемый компонент дает разработчику возможность динамически изменять, является ли вид изометрическим, а также глубину выдавливания.

struct IsometricView<Content: View>: View {
    
    var active: Bool
    var content: Content
    var extruded: Bool
    var depth: CGFloat
    
    init(active: Bool, extruded: Bool = false, depth: CGFloat = 20, @ViewBuilder content: ()-> Content) {
        self.active = active
        self.extruded = extruded
        self.depth = depth
        self.content = content()
    }
    
    @ViewBuilder var body: some View {
        if active {
            if extruded {
                content
                    .modifier(ExtrudeModifier(depth: depth, background: content))
                    .modifier(IsometricViewModifier(active: active))
                    .animation(.easeInOut)
            } else {
                content
                    .modifier(IsometricViewModifier(active: active))
                    .animation(.easeInOut)
            }
        } else {
            content
                .animation(.easeInOut)
        }
        
    }
}

Пример использования

Как только вы начнете играть с IsometricViewModifier и ExtrudeModifier, вы сможете сделать кое-что интересное. Ниже у меня есть плавающий изометрический вид, другой с двумя разными экструдированными текстурами, а также изображение.

Покажи нам, что ты сделал!

Мы хотим увидеть, что вы сделали с помощью этого урока! Присылайте нам фото! Найдите нас в Twitter @TrailingClosure, в Instagram или напишите нам по адресу [email protected].