SwiftUI dev
1.2K subscribers
87 photos
38 videos
1 file
75 links
Mobile development, SwiftUI, Compose, feel free to reach me: @lexkraev

По вопросам рекламы: @lexkraev

Статистика/цены: @lexkraev_ads
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
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
Разница в использовании async let.

The difference of using async let explained in a code example.

#readthis
🤔 Зачем нужны нестандартные фигуры (Shapes) в самых обычных SwiftUI View? Статью можно найти здесь или здесь 🙃

🤔 Why you need custom shapes for your simple SwiftUI views? Find out it here or here 🙃

#readthis
🚦🖇️ Отличная статья про диспетчеризацию в Swift

🔎🔐 Nice article about method dispatch in Swift

#readthis
🎬SwiftUI: List или LazyVStack? Статью можно найти здесь

🥁SwiftUI: List or LazyVStack? Find out it here

#readthis
Отличная статья на тему подводных камней в использовании UDF архитектур. Одним из таких является нарушение принципа LoB (Locality of Behaviour).

Nice article about pitfalls (e.g. losing the locality of behavior) in using Unidirectional architectures in swift.

#readthis
🧭 Быстрая навигация на канале

#readthis - ссылки на статьи, книги и др
#watchthis - ссылки на видео
#howto - воркшопы, обучающие статьи и т п
#getsources - ссылки на проекты с открытым исходным кодом (включая #swiftpm модули)
#trytodo - челенджи, иногда простые, иногда не очень
#groovy - посты с наибольшим количеством шарингов и реакций
#tasty - “посмотри, чтоб вдохновиться”, здесь будут анимации, концепты и т п
🧭 Quick navigation

#readthis - recommended articles, books, etc
#watchthis - recommended videos, clips, etc
#howto - tutorials, rtfm
#getsources - where the hell are sources? open-source repositories (including my own swift packages #swiftpm), projects
#trytodo - “try to do” challenges, sometimes not easy
#groovy - trending high-rated posts based on statistics (private or public sharing and positive reactions)
#tasty - cool creative features (animations, concepts, etc), might be useful for inspiring developers, designers or PMs
👨🏻‍💻 Настраиваем подключение к гиту

Статью можно найти здесь или здесь

👨🏻‍💻 Configuring git connection

Find out it here or here

Save to not to lose 😊

#readthis
Media is too big
VIEW IN TELEGRAM
💣 Мобильная разработка разделена между iOS и Android. iOS популярна на Западе, а у Android больше пользователей по всему миру.

Пренебрежение любой платформой означает отказ от большого процента потенциальных пользователей. За редким исключением приложения сначала создаются для iOS, а значит и дизайн разрабатывается сначала для iOS.

В последнее время крупные компании стараются сократить время разработки на обеих платформах. Кроссплатформенная разработка — один из способов сделать это. В моем последнем проекте мы выбрали для этого KMM. Но, будем честны, используя KMM-подход, вы сначала разрабатываете для Android-платформы, а уже потом адаптируете код для iOS. Но есть ли способ делать наоборот? Да, Skip.

Мой демо-проект с использованием Skip здесь.

🧨The mobile development is divided between iOS and Android. iOS is popular in the West, while Android has more worldwide users.

Neglecting either platform means leaving behind a large percentage of potential users. However, apps are generally made for iOS first. Clients ask for an iOS app, then expect a port to the Play Store. Actually companies design for iOS first, then adapt their designs for Android.

However large tech companies really try to reduce the development time on both platforms. Cross-platform is one of the ways to do it. On my last project we choosed KMM for this. But using KMM-approach you firstly develop for Android-platform and after that you adapt code for iOS. Is there any way to do the opposite? Yes, to use a Skip.

My demo project is here.

#getsources #howto #readthis #tasty #groovy

@swiftui_dev