В тази лекция, ще се научим как да интегрираме SwiftUI
в UIKit
проект, който няма SwiftUI
компоненти. Ще можем да изградим здрава връзка между съществуващите модели и контроли в един класически проект. След това ще се научим как да опаковаме UIView
или UIViewController
, така че да може да ги ползваме в SwiftUI
проект. Това ще ни даде възможност да изпозлваме и Canvas изгледа, за да можем да "строим" интерфейсите на своите приложения по-лесно. За завършек, ще приключим с основните Gesture детектори - абстракции, които "разпознават" различни видове жестове и активират опрделени функции в приложението, при настъпване на дадените жестове.
Голяма част от приложенията написани за iOS
, са базирани на класическата архитектура, която е стандарт за UIKit
проекти.
В тези проекти, няма SwiftUI
. Te разчитата на Storyboard
, за да дефинират потребителския интерфейс или на различни .xib
(конвертира се до .nib
) файлове.
Storyboard-ът е специален формат
XML
файл, който можем да редактираме с интерфейс билдъра - специален режим на (вече част от)Xcode
.
Без да влизаме в допълнителни детайли, трябва да споменем, че SwiftUI
е достъпен в всеки UIKit
проект. iOS
предоставя специален вю-контролер, който позволява да интегрирате произволно SwiftUI
вю. В следния пример, виждаме как се използва UIHostingController
, за да можем да покажем предварително имплементираното вю.
// хендлър, който ще бъде активиран при натискане на бутон
@IBAction func buttonPressedHandler(_ sender: Any) {
// конкретна имплементация
let swiftUIViewController = UIHostingController(rootView: InfoScreenView())
self.navigationController?.pushViewController(swiftUIViewController, animated: true)
}
Ето и конкретната имплементация на примерното InfoScreenView
:
import SwiftUI
struct InfoScreenView: View {
var body: some View {
Text("Some SwiftUI goes here")
}
}
След като имате конкретна инстанция на
UIHostingController
може да го покажете по различни начини и в правилния момент.
Може би вече се чудите, какви механизми можем да използваме, за да боравим с данни в SwiftUI
. Отгворът е - всичко, което сме научили с малки изключения (@Binding
не може да се ползва директно).
@State
и @StateObject
- за да дефинираме състояние вътре в самото вю, което може да се предава надолу по йерархията от SwiftUI
вю-та.
@EnvironmentObject
и @ObservedObject
позволяват да свържете вече съществуващия си модел (разбира се като имплементирате протокола ObservableObject
и като маркирате дадени полета за @Published
).
Полетата маркирани като
@Published
ще обновяват изгледа наSwiftUI
компонентите, ако те зависят от тях.
Ето един минимален пример, който илюстрира как можем да свържем модел със SwiftUI
вю.
моделът
вю-то
Ето така го подаваме
Ето така го подаваме към компонента и той вече е достъпен в новото вю.
Вариантът, който показахме горе не е единствения начин да интегрираме SwiftUI
компоненти в UIKit проект. Ако ползваме таблица или колекция има начин да дефинираме конкретна клетка посредством SwiftUI
(но това е по-скоро частен случай и е достъпен с iOS
16). Това нововъведение позволява по-лесно и интуитивно да се описва всяка клетка и нейния изглед с новите средства. Изграждането на интерфейса на приложението е значително по-лесно с този нов механизъм.
Без да навлизаме в излишни детайли, ще покажем примерен код, а любознателния читател, който има желание да изпозлва тази функционалност ще трябва да набави необходимите знания от документацията тук.
Обработката на данни и обновяването на клетките не е толкова лесно, но можем да изпозлваме вече добре познатия ни механизъм с
ObservableObject
.
func tableView(_ tableView: UITableView,
cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(
withIdentifier: cellReuseID,
for: indexPath
)
// приемаме, че items е колекцията от елементите,
// които показваме таблицата
let item = items[indexPath.row]
cell.contentConfiguration = UIHostingConfiguration {
HStack {
Text(item.title)
Spacer()
Image(systemName: "star.fill")
}
}
return cell
}
Трябва да отбележем, че няма вариант, в който да интегрираме конкретна инстанция на определено SwiftUI
вю, понеже то трябва да "живее" някъде. Това някъде е конкретна инстанция на UIHostingController
, който ще се грижи за всичките нужди, които има SwiftUI
.
Преди да стигнем до жестовете трябва да разберем как работи механизмът за засичане на допър върху екрана на устройството.
Съвременните устройства разполагат с капацитивни екрани. Т.е. те са изградени от разлини слове материали, които могат да улавят провеждането на нисък ток от нашите пръсти на ръцете или съответните други аксесоари, които позлваме. Не е нужно да се прилага сила, за да се засича местоположението на "пръстите" контактуващи с екрана.
От там хардуера предава на операционната система тези точки като координати, а те от своя страна стигат до нашето приложение. iOS
се грижи да предаде координатите до конкретното вю.
Ако дадено вю улавя "тъчовете", то вю-тата под него няма да получават никакви тъчове. (Това поведени може да бъде променено при желание от страна на програмистта, ако е необходимо.)
Ето и един пример, който премахва възможността текст-а да хваща "тъчовете". Те минават директно към вю-то под него.
Text("Hello.").allowsHitTesting(false)
Добре вече ние ясно, че със своите действия (комуникация) с устройстовто, имаме множество от тъчове. Т.е. можем да си представим един поток от специални ивенти, които всъщност са координати по екрана и показват кой пръст точно ги е създал.
iPhone
може да следи до пет пръста още от самото начало. A `iPadѕ до десет във всеки един момент.
Тогава много лесно можем да обясним какво са жестовите детектори - това са коклретни структури, които позовляват разпознаването на конкретни последователности в този поток от "тъч" (touch) данни. При разпознаване на тази последователност имаме възможност да активираме някаква функция. Това е най-общия вид на тези обекти.
iOS идва с няколко различни жестови детектори. Но нека да разгледаме най-основния.
ТаpGesture
- той ни позовлява да засечем, кога потребителя тап-ва по опделен елемент.
Ето и конкретен пример:
struct ContentView: View {
@State private var scale = 1.0
var body: some View {
VStack {
Text("Gestures!")
.font(.title)
Image(systemName: "iphone")
.imageScale(.large)
.foregroundColor(.accentColor)
.background(Color.gray)
.scaleEffect(scale)
.gesture(
TapGesture()
.onEnded { _ in
scale += 0.1
}
)
}
.padding()
}
}
Всички детектори на жестове (жест-детектори) добавяме чрез модификатора gesture()
. Можем да използваме конструктора да зададем конкретна конфигурация на съответния жест-детектор. А с onEnded
модификаторът, подаваме клоужър, който се изпълнява при засичане на съответния жест.