👍🏻 Весьма годная статья про акторы
https://apptractor.ru/info/articles/actors-swift-5-5.html
Выдержка:
Одно из лучших объяснений для общения в модели акторов выглядит следующим образом:
Представьте, что каждый актор похож на остров, а наша кодовая база — это мир с островами. Каждый остров может общаться с другим островом, отправляя ему сообщения в бутылке. Каждый остров знает, куда отправить сообщение (то есть адрес другого острова), и именно так работает связь между островами.
#readthis
https://apptractor.ru/info/articles/actors-swift-5-5.html
Выдержка:
Одно из лучших объяснений для общения в модели акторов выглядит следующим образом:
Представьте, что каждый актор похож на остров, а наша кодовая база — это мир с островами. Каждый остров может общаться с другим островом, отправляя ему сообщения в бутылке. Каждый остров знает, куда отправить сообщение (то есть адрес другого острова), и именно так работает связь между островами.
#readthis
AppTractor
Погружение в Акторы в Swift 5.5
Давайте посмотрим на изменения в параллелизме в новой версии Swift.
SwiftUI dev
Modern swift API design.pdf
This media is not supported in your browser
VIEW IN TELEGRAM
Главное:
1.
2. Компилятор раскрывает property wrapper в stored property/storage (сохраняемое состояние) и computed property (вычисляемое состояние), в случае с
3. Именно из storage SwiftUI и отрисовывает
4. У
5. Привязка
#readthis
1.
Binding
, State
- это property wrappers2. Компилятор раскрывает property wrapper в stored property/storage (сохраняемое состояние) и computed property (вычисляемое состояние), в случае с
Binding
storage ссылается на storage State
в геттере.3. Именно из storage SwiftUI и отрисовывает
View
после изменения Binding
или State
.4. У
Binding
дополнительно введен аттрибут dynamicMemberLookup
(который вызывает subscript при доступе к свойству: $slide.title -> $slide[dynamicMemder: \Slide.title]
)5. Привязка
Binding
к State
работает через префикс $
#readthis
This media is not supported in your browser
VIEW IN TELEGRAM
Наглядный пример, почему Apple депрекэйтнула модификатор
#readthis
.animation
с одним параметром (типом анимации): из-за некорректной отрисовки самой анимации, SUI не понимает, на какое именно событие анимировать. 👇🏻#readthis
SwiftUI dev
Наглядный пример, почему Apple депрекэйтнула модификатор .animation с одним параметром (типом анимации): из-за некорректной отрисовки самой анимации, SUI не понимает, на какое именно событие анимировать. 👇🏻 #readthis
Рассмотрим, код:
Предположим, хотим сделать zoom-zoom анимацию при тапе на кнопку. Для этого сделаем модификатор:
Далее применим этот модификатор для отрисовки элемента в горизонтальном Lazy стэке:
В итоге получаем представленный баг: некорректная анимация на onAppear в lazy стеке. Фиксим путем добавления
#readthis
Предположим, хотим сделать zoom-zoom анимацию при тапе на кнопку. Для этого сделаем модификатор:
public struct ScaleButtonStyle: ButtonStyle {
public init() {}
public func makeBody(configuration: Self.Configuration) -> some View {
configuration.label
.scaleEffect(configuration.isPressed ? 0.95 : 1)
.animation(.linear(duration: 0.2))
.brightness(configuration.isPressed ? -0.05 : 0)
}
}
public extension ButtonStyle where Self == ScaleButtonStyle {
static var scale: ScaleButtonStyle {
ScaleButtonStyle()
}
}
Далее применим этот модификатор для отрисовки элемента в горизонтальном Lazy стэке:
ScrollView(.horizontal, showsIndicators: false) {
LazyHStack {
ForEach(data, id: \.self) { card in
Button {
…
} label: {
VStack(alignment: .leading
) {
AsyncImage(…)
.resizable()
.frame(width: elementSize.width, height: elementSize.height)
.cornerRadius(8)
Text(…)
.boldFont(…)
.foregroundColor(…)
}
}
.buttonStyle(ScaleButtonStyle())
}
}
В итоге получаем представленный баг: некорректная анимация на onAppear в lazy стеке. Фиксим путем добавления
value
в .animation
, тем самым указывая конкретное значение для отслеживания изменений.public struct ScaleButtonStyle: ButtonStyle {
public init() {}
public func makeBody(configuration: Self.Configuration) -> some View {
configuration.label
.scaleEffect(configuration.isPressed ? 0.95 : 1)
.animation(.linear(duration: 0.2), value: configuration.isPressed)
.brightness(configuration.isPressed ? -0.05 : 0)
}
}
#readthis
List vs LazyVStack
Что лучше использовать и когда?
Зависит, конечно, от поставленной задачи. Например, если приложение на UIKit, и хотите подтянуть SwiftUI, то необходимо учитывать, что разделители у ячеек в List, если они не нужны, до iOS 15 убираются весьма костыльным способом:
Тем самым можно зааффектить дизайн UITableView во всем приложении. В iOS 15 для этого уже появился специальный модификатор
Но, на мой взгляд, ключевой особенностью является рендеринг у
Начиная с iOS 15, List рендерит уже только элементы, видимые на экране. Получается, что пагинацию на List для iOS < 15 можно сделать только, если загружать > 20 элементов. Это не всем может подойти, в приоритете окажется LazyVStack.
Which one is best to use and when?
It depends, of coz, on the task. For ex, if the app based on on UIKit, and you want to add SwiftUI, then you should be considered that the separators between cells in the List, if they are not needed, are removed in a very “crutch” way before iOS 15:
This can affect the design of the UITableView throughout the whole application. Beginning with iOS 15 a special modifier
Imho the key feature is the difference in the rendering (which depend on iOS versions) of
Beginning with iOS 15 List renders only items that are visible on the screen. Turns out that in iOS < 15 using List pagination can only be done if you should load > 20 items. This may not suit for everyone and LazyVStack should be used.
#readthis
Что лучше использовать и когда?
Зависит, конечно, от поставленной задачи. Например, если приложение на UIKit, и хотите подтянуть SwiftUI, то необходимо учитывать, что разделители у ячеек в List, если они не нужны, до iOS 15 убираются весьма костыльным способом:
.onAppear {
UITableView.appearance().separatorStyle = .none
}
Тем самым можно зааффектить дизайн UITableView во всем приложении. В iOS 15 для этого уже появился специальный модификатор
.listRowSeparator(.hidden)
.Но, на мой взгляд, ключевой особенностью является рендеринг у
List
и LazyVStack
, который отличается в зависимости от версий iOS. В iOS < 15 List рендерит ячейки “с запасом”, а именно, если List растянут на весь экран, то ленивой загрузки нет у первых 15 (iPhone 6s) - 20 элементов, при этом без разницы, какая высота фрейма у элемента. Напротив, LazyVStack рендерит только те элементы, которые видны на экране. Таким образом, например, вешая модификатор .onAppear{…}
, мы получаем ожидаемое поведение только у LazyVStack. Начиная с iOS 15, List рендерит уже только элементы, видимые на экране. Получается, что пагинацию на List для iOS < 15 можно сделать только, если загружать > 20 элементов. Это не всем может подойти, в приоритете окажется LazyVStack.
Which one is best to use and when?
It depends, of coz, on the task. For ex, if the app based on on UIKit, and you want to add SwiftUI, then you should be considered that the separators between cells in the List, if they are not needed, are removed in a very “crutch” way before iOS 15:
.onAppear {
UITableView.appearance().separatorStyle = .none
}
This can affect the design of the UITableView throughout the whole application. Beginning with iOS 15 a special modifier
.listRowSeparator(.hidden)
has already appeared for this.Imho the key feature is the difference in the rendering (which depend on iOS versions) of
List
and LazyVStack
. In iOS < 15 List renders cells “with a margin”, namely, if the List is stretched to full screen, then the first 15 (iPhone 6s) - 20 elements do not have lazy loading, and it doesn’t matter what the element’s frame height is. Opp. LazyVStack renders only elements that are visible on the screen. Thus, for ex, by setting the .onAppear{…}
modifier, we get the expected behavior only for LazyVStack
.Beginning with iOS 15 List renders only items that are visible on the screen. Turns out that in iOS < 15 using List pagination can only be done if you should load > 20 items. This may not suit for everyone and LazyVStack should be used.
#readthis
This media is not supported in your browser
VIEW IN TELEGRAM
Весьма интересный момент отрисовки анимации в SwiftUI 👇🏻
Interesting case of SwiftUI-animation rendering👇🏻
#readthis
Interesting case of SwiftUI-animation rendering👇🏻
#readthis
SwiftUI dev
Весьма интересный момент отрисовки анимации в SwiftUI 👇🏻 Interesting case of SwiftUI-animation rendering👇🏻 #readthis
Рассмотрим следующий код:
Весьма стандартная ситуация: имеется скролл вью с листом, при выборе из этого листа объекта нужно анимировать (в данном случае, просто поменять
Возможны несколько вариантов фикса.
Один из которых ввести искусственную задержку, например, в 0.2 - 0.3 секунды, не заметную для пользователя, но ее будет хватать на загрузку изображения:
В большинстве случаев, этот фикс работает, но при плохом интернете, понятно, что нет.
Предпочтительным, на мой взгляд, будет другой сценарий: возвращать стейт загрузки изображения:
#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