16 полезных расширений для SwiftUI

1. Hide

Это модификатор представлений, позволяющий показывать и скрывать их, что, как правило, недоступно. Стоит отметить, что модификатор представлений  —  очень полезный паттерн, который не помешает взять на заметку.

struct Show: ViewModifier {
let isVisible: Bool

@ViewBuilder
func body(content: Content) -> some View {
if isVisible {
content
} else {
content.hidden()
}
}
}

Используйте его просто как модификатор представления, а условной переменной будет булево значение.

.modifier(Show(isVisible: condition))

Как видите, модификатор освобождает место на view и обеспечивает перерисовку, что повышает производительность. В качестве альтернативы можно использовать вкладку opacity. Это позволит ускорить работу, но не освободит место, которое было использовано.

2. Branch

Этот модификатор является идеальным решением для управления включением/исключением атрибутов.

extension View {
@ViewBuilder
func `if`<Transform: View>(_ condition: Bool, transform: (Self) -> Transform) -> some View {
if condition { transform(self) }
else { self }
}
}

Используемый код (с переменной внутри) отмечен в приведенном ниже примере:

.if(colored) { view in
view.background(Color.blue)
}

3. Print

Одна из первых функций, которую стоит усвоить новичку в SwiftUI,  —  это print. Возможно, это “динозавр” среди способов отладки, но он все еще входит в учебные программы. Поэтому следующий фрагмент кода просто бесценен:

extension View {
func Print(_ vars: Any...) -> some View {
for v in vars { print(v) }
return EmptyView()
}
}

Такая запись позволяет использовать в коде утверждение, подобное этому:

self.Print("Inside ForEach", varOne, varTwo ...)

4. Delay

Delay  —  нововведение в iOS15. Хотя оно и не является функцией SwiftUI, ее все-таки можно использовать в коде.

extension Task where Success == Never, Failure == Never {
static func sleep(seconds: Double) async throws {
let duration = UInt64(seconds * 1000_000_000)
try await sleep(nanoseconds: duration)
}
}

Конечно, код, который надо запустить после задержки, должен следовать за Task, которое само создает задержку.

Task { try! await Task.sleep(seconds: 0.5) }

5. PassThruSubjects

Начав использовать Combine со SwiftUI, я обнаружил, что PassThroughSubjects  —  очень полезный способ связать старое с новым. Я написал статью о том, как можно использовать их для покупок в приложениях. Однако они часто срабатывают более одного раза при отправке. Приведенный ниже код поможет решить эту проблему:

let changeColor = PassthroughSubject<Int,Never>()

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

.onReceive(signalButton
.eraseToAnyPublisher()
.throttle(for: .milliseconds(10), scheduler: RunLoop.main, latest: true))
{ value in
if value == 2 {
button2 = true
levelColor = Color.red
}
}

В данном случае calling используется для того, чтобы обозначить числом один объект. Это позволит запустить несколько веток в одном и том же коде SwiftUI.

changeColor.send(3)

6. Subscriptions

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

let cameraGesture = PassthroughSubject<cameraActions,Never>()
var cameraSubscription:AnyCancellable? = nil

Затем потребуются два объявления combine:

cameraSubscription = cameraGesture
.eraseToAnyPublisher()
.throttle(for: .milliseconds(10), scheduler: RunLoop.main, latest: true)
.sink(receiveValue: { value in
// делаем что-нибудь со значением
})

Отправка сообщения combine, как и ранее:

cameraGesture.send(._1orbitTurntable)

7. Timer

publisher  —  отличный способ создания таймера. Вот код:

let timer = Timer.publish(every: 1, on: .main, in: .common).autoconnect()

Примечание. Значение времени находится в переменной _ unused. Этот способ работает более надежно по сравнению с send, так как отправляется одно сообщение, а не несколько.

.onReceive(timer) { _ in
// какие-либо действия
}

8. Coordinates/Size

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

struct returnSize: View {
var body: some View {
GeometryReader { geometry in
Color.clear
.onAppear(perform: {
print("geo \(geometry.size)")
})
}
}
}

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

.background(returnSize())

9. Attributes Strings

Это очень полезное расширение, которое я нашел на Stack Overflow. Оно применяется к атрибутам объектов Text в iOS15.

extension Text {
init(_ string: String, configure: ((inout AttributedString) -> Void)) {
var attributedString = AttributedString(string) /// create an `AttributedString`
configure(&attributedString)
self.init(attributedString)
}
}

Приведенные выше сниппеты можно использовать следующим образом:

Text("GAME OVER") { $0.kern = CGFloat(2) }

10. AnyView

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

extension View {
func eraseToAnyView() -> AnyView {
AnyView(self)
}
}

Вот пример расширения, которое позволяет вернуть текстовый элемент или изображение:

struct returnDifferentViews: View {
@State var means:Bool
var body: some View {
if means {
return Image("1528")
.eraseToAnyView()
} else {
return Text("1528")
.eraseToAnyView()
}
}
}

11. SubScript

Это расширение позволяет индицировать строки с помощью давно устоявшегося стандарта.

extension String {
var length: Int {
return count
}
subscript (i: Int) -> String {
return self[i ..< i + 1]
}
func substring(fromIndex: Int) -> String {
return self[min(fromIndex, length) ..< length]
}
func substring(toIndex: Int) -> String {
return self[0 ..< max(0, toIndex)]
}
subscript (r: Range<Int>) -> String {
let range = Range(uncheckedBounds: (lower: max(0, min(length, r.lowerBound)),
upper: min(length, max(0, r.upperBound))))
let start = index(startIndex, offsetBy: range.lowerBound)
let end = index(start, offsetBy: range.upperBound - range.lowerBound)
return String(self[start ..< end])
}
}

Этот код можно применять так же, как и в других языках.

let word = "Start"
for i in 0..<word.length {
print(word[i])
}

12. Detect a Shake

Ловлю себя на мысли, что постоянно ищу этот код. Как можно запомнить такую последовательность?

extension NSNotification.Name {
public static let deviceDidShakeNotification = NSNotification.Name("MyDeviceDidShakeNotification")
}
extension UIWindow {
open override func motionEnded(_ motion: UIEvent.EventSubtype, with event: UIEvent?) {
super.motionEnded(motion, with: event)
NotificationCenter.default.post(name: .deviceDidShakeNotification, object: event)
}
}
extension View {
func onShake(perform action: @escaping () -> Void) -> some View {
self.modifier(ShakeDetector(onShake: action))
}
}
struct ShakeDetector: ViewModifier {
let onShake: () -> Void
func body(content: Content) -> some View {
content
.onAppear() // this has to be here because of a SwiftUI bug
.onReceive(NotificationCenter.default.publisher(for:
.deviceDidShakeNotification)) { _ in
onShake()
}
}
}

Расширение можно создать следующим образом:

.onShake {
print("stop it shaking")
}

13. Snapshot a View

Это ценное расширение взято с сайта HWS  —  отличного источника всей информации о Swift.

extension View {
func snapshot() -> UIImage {
let controller = UIHostingController(rootView: self)
let view = controller.view

let targetSize = controller.view.intrinsicContentSize
view?.bounds = CGRect(origin: .zero, size: targetSize)
view?.backgroundColor = .clear

let renderer = UIGraphicsImageRenderer(size: targetSize)

return renderer.image { _ in
view?.drawHierarchy(in: controller.view.bounds, afterScreenUpdates: true)
}
}
}

Может использоваться следующим образом:

let image = textView.snapshot().ignoresSafeArea

14. Saving/Loading Images

Я нашел это расширение на портале разработчиков Apple. Оно может пригодиться и вам.

extension URL {
func loadImage(_ image: inout UIImage) {
if let loaded = UIImage(contentsOfFile: self.path) {
image = loaded
}
}
func saveImage(_ image: UIImage) {
if let data = image.jpegData(compressionQuality: 1.0) {
try? data.write(to: self)
}
}
}
https://developer.apple.com/forums/thread/661144

В самом расширении код можно использовать следующим образом:

@State private var image = UIImage(systemName: "xmark")!
private var url: URL { let paths = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask) return paths[0].appendingPathComponent("image.jpg")}
var body: some View { 
Image(uiImage: image)
.onAppear { url.load(&image) }
.onTapGesture { url.save(image) }
}

15. List Fonts

Следующее расширение тоже очень полезно.

let fontFamilyNames = UIFont.familyNames
for familyName in fontFamilyNames {
print("Font Family Name = [\(familyName)]")
let names = UIFont.fontNames(forFamilyName: familyName)
print("Font Names = [\(names)]")
}

С помощью кода SwiftUI можно находить шрифты следующим образом:

struct Fonts {
static func avenirNextCondensedBold (size: CGFloat) -> Font {
return Font.custom("AvenirNextCondensed-Bold", size: size)
}

Чтобы использовать код, можно вызывать эту конструкцию:

.font(Fonts.avenirNextCondensedBold(size: 12))

16. Ternary Operator

Это расширение может использоваться во многих областях SwiftUI.

Тернарный оператор оценивает condition и:

  • если condition верно (true), выполняется expression1.
  • если condition ложно (false), выполняется expression2.

Тернарный оператор принимает три операнда (condition, expression1 и expression2). Отсюда и название  —  троичный (тернарный) оператор.

flipColor = flipColor == .blue ? .green : .blue

Таким образом, если flipColor синий (blue), мы получим зеленый (green), а если flipColor зеленый (green), то синий (blue).

Читайте также:

Читайте нас в TelegramVK и Яндекс.Дзен


Перевод статьи Mark Lucking: 16 Useful Extensions for SwiftUI

Предыдущая статьяСовместное использование компонентов React с Webpack 5
Следующая статья7 полезных методов объектов JavaScript