SwiftUI dev
1.17K subscribers
87 photos
37 videos
1 file
74 links
Mobile development, SwiftUI, Compose, feel free to reach me: @lexkraev
Download Telegram
This media is not supported in your browser
VIEW IN TELEGRAM
Весьма интересный момент отрисовки анимации в SwiftUI 👇🏻
Interesting case of SwiftUI-animation rendering👇🏻

#readthis
SwiftUI dev
Весьма интересный момент отрисовки анимации в SwiftUI 👇🏻 Interesting case of SwiftUI-animation rendering👇🏻 #readthis
Рассмотрим следующий код:

...
@State var selectedGood: Good?

var body: some View {
ZStack(alignment: .bottom) {
// List with goods
ScrollList(selection: $selectedGood)
.zIndex(0)

// animated panel view:
...
AsyncImage(url: URL(string: selectedGood.image.url)) { image in
image.resizable()
.aspectRatio(contentMode: .fit)
} placeholder: {
Color.Background.secondary.cornerRadius(8)
}
...
.offset(y: selectedGood != nil ? 0 : -2 * height)
.animation(.spring(…), value: selectedGood != nil)
.zIndex(1)
}
}
...

Весьма стандартная ситуация: имеется скролл вью с листом, при выборе из этого листа объекта нужно анимировать (в данном случае, просто поменять offset) панель. Менять offset начинаем сразу после выбора. На панели выведены два изображения, одно из которых выбранный объект из списка. Изображения рендерим через AsyncImage, используя placeholder. Если кэша изображения нет, то какое-то время необходимо потратить на его загрузку по сети. Здесь и сталкиваемся с неожиданным поведением отрисовки анимации в SwiftUI: замена placeholder-а на изображение происходит в финальной точке положения View после анимации, а не во время. Сразу укажу, что добавление .delay на анимацию ситуацию не исправляет.
Возможны несколько вариантов фикса.
Один из которых ввести искусственную задержку, например, в 0.2 - 0.3 секунды, не заметную для пользователя, но ее будет хватать на загрузку изображения:

...
@State var startAnimation: Bool = false
@State var selectedGood: Good?
...

AsyncImage(url: URL(string: selectedGood.image.url)) { image in
image.resizable()
.aspectRatio(contentMode: .fit)
} placeholder: {
Color.Background.secondary.cornerRadius(8)
}.onChange(of: selectedGood) { _ in
DispatchQueue.main.asyncAfter(deadline: .now() + 0.2) {
withAnimation {
startAnimation = true
}
}
}
.offset(y: startAnimation ? 0 : -2 * height)
.animation(.spring(...), value: startAnimation)
...

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

...
@State var startAnimation: Bool = false
@State var selectedGood: Good?
...

CustomAsyncImage(url: URL(string: selectedGood.image.url),
startAnimation: $startAnimation ) { image in
image.resizable()
.aspectRatio(contentMode: .fit)
} placeholder: {
Color.Background.secondary.cornerRadius(8)
}.offset(y: startAnimation ? 0 : -2 * height)
.animation(.spring(...), value: startAnimation)
...




struct CustomAsyncImage: View {
...
@Binding var startAnimation: Bool
...
var body: some View {
AsyncImage(url: URL(string: url)) { image in
image.resizable()
.aspectRatio(contentMode: .fit)
.onAppear {
isLoaded.toggle()
}
} placeholder: {
Color.Background.secondary.cornerRadius(8)
}
}
}
...


#readthis
SwiftUI dev
Весьма интересный момент отрисовки анимации в SwiftUI 👇🏻 Interesting case of SwiftUI-animation rendering👇🏻 #readthis
Look at following code:

...
@State var selectedGood: Good?

var body: someView {
ZStack(alignment: .bottom) {
// List with goods
ScrollList(selection: $selectedGood)
.zIndex(0)

// animated panel view:
...
AsyncImage(url: URL(string: selectedGood.image.url)) { image in
image.resizable()
.aspectRatio(contentMode: .fit)
} placeholder: {
Color.Background.secondary.cornerRadius(8)
}
...
.offset(y: selectedGood != nil ? 0 : -2 * height)
.animation(.spring(...), value: selectedGood != nil)
.zIndex(1)
}
}
...


Very standard case: there is a scroll view with a panel view, when you select an object from list, you should animate (in this case, just change the offset) the panel view. We start changing offset immediately after selection. The panel displays two images, one of which is the selected object from the list. Images are rendered via AsyncImage using placeholder. If there is no image cache then some time should be spent for downloading. This is where unexpected rendering animation behavior in SwiftUI appears: the placeholder is replaced with an image at the final position of the View after the animation (not during it). I’ll point out that adding .delay to the animation does not fix the situation.
Several options for fix this are possible.
One of which is to add some delay, for example, in 0.2 - 0.3 seconds, which is not noticeable for user but it will be enough to load the image:

...
@State var startAnimation: Bool = false
@State var selectedGood: Good?
...

AsyncImage(url: URL(string: selectedGood.image.url)) { image in
image.resizable()
.aspectRatio(contentMode: .fit)
} placeholder: {
Color.Background.secondary.cornerRadius(8)
}.onChange(of: selectedGood) { _ in
DispatchQueue.main.asyncAfter(deadline: .now() + 0.2) {
withAnimation {
startAnimation = true
}
}
}
.offset(y: startAnimation ? 0 : -2 * height)
.animation(.spring(...), value: startAnimation)
...


In most cases this fix works but not with poor Internet.
IMHO the preferred fix option would be to return the state of the image loading:

...
@State var startAnimation: Bool = false
@State var selectedGood: Good?
...

CustomAsyncImage(url: URL(string: selectedGood.image.url),
startAnimation: $startAnimation ) { image in
image.resizable()
.aspectRatio(contentMode: .fit)
} placeholder: {
Color.Background.secondary.cornerRadius(8)
}.offset(y: startAnimation ? 0 : -2 * height)
.animation(.spring(...), value: startAnimation)
...



struct CustomAsyncImage: View {
...
@Binding var startAnimation: Bool
...
var body: someView {
AsyncImage(url: URL(string: url)) { image in
image.resizable()
.aspectRatio(contentMode: .fit)
.onAppear {
isLoaded.toggle()
}
} placeholder: {
Color.Background.secondary.cornerRadius(8)
}
}
}
...


#readthis
Проект с примерами различных анимаций на SwiftUI с лицензией бесплатного коммерческого использования.

A repository containing a variety of animations and Animated components created in SwiftUI that you can use in your own projects.

#howto #getsources
Patched Yandex maps mobile as SwiftPM working on Xcode > 13.3 on M1, however waiting for official release announced last week))

Пропатченная версия Яндекс карт, собранная как SwiftPM, работающая на Xcode старше 13.3 на M1, так или иначе ждём официального релиза, который пообещали на прошлой неделе))

UPD: upgraded to Yandex maps mobile 4.1.0, worked on Apple Silicone without rosetta mode, manual is here.

Обновил до 4.1.0, полноценно работает на m1, мануал по поддержке здесь.

UPD2: add Yandex lite maps mobile

Добавил Yandex maps mobile версии lite

#switfpm #getsources
This media is not supported in your browser
VIEW IN TELEGRAM
Possible implementation of waterfall grid with row spanning-trick effect based on LazyVStack.

Реализация грида с ячейками разной высоты на LazyVStack.

#howto #getsources
This media is not supported in your browser
VIEW IN TELEGRAM
Implementation SwiftUI tab bar instead of UIKit’s.

Написал статью, как мы внедряли SwiftUI таб-бар взамен UIKit.

#readthis #howto
Статья на тему, как лучше подключать тяжелые зависимости в SwiftPM на примере Firebase.
От себя хочу сказать, что подключив Firebase как набор XCFramework файлов через бинарную зависимость в SwiftPM, скорость сборки проекта (после очистки кэша) повысилась на 15%: со 184 сек до 157.
Репозиторий с бинарниками Firebase.

Nice article on how to add heavy dependencies like Firebase using SwiftPM.
On my own I have to say that we improved Xcode project build time from 184 sec to 157 sec (15%) after adding Firebase as XCFrameworks as .binaryTarget in package.
Repository with Firebase binaries is here.

#switfpm #readthis
This media is not supported in your browser
VIEW IN TELEGRAM
Увидел этот пример в ленте linkedin (первоисточник — Eran Lunenfeld, который реализовал на jetpack compose). Решил воспроизвести сам 😊 Думаю, хороший челендж и пример для тестовых заданий на SwiftUI. Попробуйте сверстать сами: есть интересные моменты. Посмотреть код здесь

Saw this example in news feed on linkedin, author was Shai Mishali (who originally saw it from Eran Lunenfeld in his turn) and decided to reproduce myself ))
Think it was nice challenge ))
Try to make it yourself! Anyway code is here

#trytodo #howto #tasty #groovy #getsources
This media is not supported in your browser
VIEW IN TELEGRAM
Анимация, вдохновленная Spotify. Отличный пример для тестовых заданий на SUI. Попробуйте воспроизвести сами! Посмотреть код здесь

Animation challenge inspired by Spotify. Try to make it yourself! You may find code here

#tasty #getsources
This media is not supported in your browser
VIEW IN TELEGRAM
AirDrop анимация. Попробуйте воспроизвести сами 💻 Посмотреть код здесь

AirDrop animation challenge. Try to make it yourself 💻 You may find code here

#tasty #getsources
This media is not supported in your browser
VIEW IN TELEGRAM
Канал вырос до 200 подписчиков! Спасибо каждому!☺️ Фейерверк-анимация для вас!🎇🎆 Код здесь

Thanks for 200 subscribers!☺️ Fireworks animation 4u!🎇🎆 Code is here

#tasty #getsources
This media is not supported in your browser
VIEW IN TELEGRAM
Xcode multi-cursor feature

Think everybody knows about this but whatever )))
This media is not supported in your browser
VIEW IN TELEGRAM
По-умолчанию, если вы добавляете кнопку в ячейку, вся ячейка будет реагировать на action этой кнопки.

Для ожидаемого поведения используйте модификатор .buttonStyle(PlainButtonStyle()) (или .buttonStyle(BorderedButtonStyle()), доступный с iOS 15)

By default, if you add a button to a SwiftUI's cell, all the cell's area will react to button's action.

To fix this, use .buttonStyle(PlainButtonStyle()) modifier (or .buttonStyle(BorderedButtonStyle()) available from iOS 15)

#howto
This media is not supported in your browser
VIEW IN TELEGRAM
Синтаксис if-let (и guard-let) в Swift 5.7 стал проще

New in Swift 5.7, the if-let syntax is even shorter 🔥 (it also works with guard-let)
This media is not supported in your browser
VIEW IN TELEGRAM
Расширяющиеся карточки 🌄🌅 Посмотреть код здесь

Expanding flex cards using SwiftUI 🌄🌅 You may find code here

#trytodo #tasty #groovy #getsources
This media is not supported in your browser
VIEW IN TELEGRAM
Именно так, начиная с xcode 13, можно поставить breakpoint внутри такого замыкания 👨🏻‍💻

You can use special tool - Column Breakpoints - to set a breakpoint inside that kind of closure 👨🏻‍💻
This media is not supported in your browser
VIEW IN TELEGRAM
Варианты открытий кнопки меню в виде 🍔 Код здесь

Cooking hamburgers 🍔 Code is here

#tasty #getsources
Media is too big
VIEW IN TELEGRAM
Кастомизируем таб бар Код можно найти здесь

Customising and animating tab bar 🌊 using SwiftUI Code is here

#howto #tasty #groovy #getsources

@swiftui_dev