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
).
Читайте также:
- Как создать пользовательскую поисковую панель SwiftUI с LazyVStack
- Создание кастомного навигационного представления в SwiftUI
- Построение бесконечного списка с помощью SwiftUI и Combine
Читайте нас в Telegram, VK и Яндекс.Дзен
Перевод статьи Mark Lucking: 16 Useful Extensions for SwiftUI