SwiftUI.swift 106 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363236423652366236723682369237023712372237323742375237623772378237923802381238223832384238523862387238823892390239123922393239423952396239723982399240024012402240324042405240624072408240924102411241224132414241524162417241824192420242124222423242424252426242724282429243024312432243324342435243624372438243924402441244224432444244524462447244824492450245124522453245424552456
  1. ////////////////////////////////////////////////////////////////////////////
  2. //
  3. // Copyright 2021 Realm Inc.
  4. //
  5. // Licensed under the Apache License, Version 2.0 (the "License");
  6. // you may not use this file except in compliance with the License.
  7. // You may obtain a copy of the License at
  8. //
  9. // http://www.apache.org/licenses/LICENSE-2.0
  10. //
  11. // Unless required by applicable law or agreed to in writing, software
  12. // distributed under the License is distributed on an "AS IS" BASIS,
  13. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14. // See the License for the specific language governing permissions and
  15. // limitations under the License.
  16. //
  17. ////////////////////////////////////////////////////////////////////////////
  18. import Foundation
  19. #if !(os(iOS) && (arch(i386) || arch(arm)))
  20. import SwiftUI
  21. import Combine
  22. import Realm
  23. import Realm.Private
  24. private func write<Value>(_ value: Value, _ block: (Value) -> Void) where Value: ThreadConfined {
  25. let thawed = value.realm == nil ? value : value.thaw() ?? value
  26. if let realm = thawed.realm, !realm.isInWriteTransaction {
  27. try! realm.write {
  28. block(thawed)
  29. }
  30. } else {
  31. block(thawed)
  32. }
  33. }
  34. private func thawObjectIfFrozen<Value>(_ value: Value) -> Value where Value: ObjectBase & ThreadConfined {
  35. return value.realm == nil ? value : value.thaw() ?? value
  36. }
  37. @available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *)
  38. private func createBinding<T: ThreadConfined, V>(
  39. _ value: T,
  40. forKeyPath keyPath: ReferenceWritableKeyPath<T, V>) -> Binding<V> {
  41. guard let value = value.isFrozen ? value.thaw() : value else {
  42. throwRealmException("Could not bind value")
  43. }
  44. // store last known value outside of the binding so that we can reference it if the parent
  45. // is invalidated
  46. var lastValue = value[keyPath: keyPath]
  47. return Binding(get: {
  48. guard !value.isInvalidated else { return lastValue }
  49. lastValue = value[keyPath: keyPath]
  50. return lastValue
  51. }, set: { newValue in
  52. guard !value.isInvalidated else { return }
  53. write(value) { value in
  54. value[keyPath: keyPath] = newValue
  55. }
  56. })
  57. }
  58. @available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *)
  59. private func createCollectionBinding<T: ThreadConfined, V: RLMSwiftCollectionBase & ThreadConfined>(
  60. _ value: T,
  61. forKeyPath keyPath: ReferenceWritableKeyPath<T, V>) -> Binding<V> {
  62. guard let value = value.isFrozen ? value.thaw() : value else {
  63. throwRealmException("Could not bind value")
  64. }
  65. var lastValue = value[keyPath: keyPath]
  66. return Binding(get: {
  67. guard !value.isInvalidated else { return lastValue }
  68. lastValue = value[keyPath: keyPath]
  69. if lastValue.realm != nil {
  70. lastValue = lastValue.freeze()
  71. }
  72. return lastValue
  73. }, set: { newValue in
  74. guard !value.isInvalidated else { return }
  75. write(value) { value in
  76. value[keyPath: keyPath] = newValue
  77. }
  78. })
  79. }
  80. @available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *)
  81. private func createEquatableBinding<T: ThreadConfined, V: Equatable>(
  82. _ value: T,
  83. forKeyPath keyPath: ReferenceWritableKeyPath<T, V>) -> Binding<V> {
  84. guard let value = value.isFrozen ? value.thaw() : value else {
  85. throwRealmException("Could not bind value")
  86. }
  87. var lastValue = value[keyPath: keyPath]
  88. return Binding(get: {
  89. guard !value.isInvalidated else { return lastValue }
  90. lastValue = value[keyPath: keyPath]
  91. return lastValue
  92. }, set: { newValue in
  93. guard !value.isInvalidated else { return }
  94. guard value[keyPath: keyPath] != newValue else { return }
  95. write(value) { value in
  96. value[keyPath: keyPath] = newValue
  97. }
  98. })
  99. }
  100. // MARK: SwiftUIKVO
  101. @available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *)
  102. @objc(RLMSwiftUIKVO) internal final class SwiftUIKVO: NSObject {
  103. /// Objects must have observers removed before being added to a realm.
  104. /// They are stored here so that if they are appended through the Bound Property
  105. /// system, they can be de-observed before hand.
  106. @Unchecked
  107. fileprivate static var observedObjects = [NSObject: SwiftUIKVO.Subscription]()
  108. @available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *)
  109. struct Subscription: Combine.Subscription {
  110. let observer: NSObject
  111. let value: NSObject
  112. let keyPaths: [String]
  113. var combineIdentifier: CombineIdentifier {
  114. CombineIdentifier(value)
  115. }
  116. func request(_ demand: Subscribers.Demand) {
  117. }
  118. func cancel() {
  119. removeObservers()
  120. SwiftUIKVO.observedObjects.removeValue(forKey: value)
  121. }
  122. fileprivate func removeObservers() {
  123. guard SwiftUIKVO.observedObjects.keys.contains(value) else {
  124. return
  125. }
  126. keyPaths.forEach {
  127. value.removeObserver(observer, forKeyPath: $0)
  128. }
  129. }
  130. fileprivate func addObservers() {
  131. guard SwiftUIKVO.observedObjects.keys.contains(value) else {
  132. return
  133. }
  134. keyPaths.forEach {
  135. value.addObserver(observer, forKeyPath: $0, options: .init(), context: nil)
  136. }
  137. }
  138. }
  139. private let receive: () -> Void
  140. override func observeValue(forKeyPath keyPath: String?,
  141. of object: Any?,
  142. change: [NSKeyValueChangeKey: Any]?,
  143. context: UnsafeMutableRawPointer?) {
  144. receive()
  145. }
  146. init<S>(subscriber: S) where S: Subscriber, S.Input == Void {
  147. receive = { _ = subscriber.receive() }
  148. super.init()
  149. }
  150. }
  151. // MARK: - ObservableStorage
  152. @available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *)
  153. private final class ObservableStoragePublisher<ObjectType>: Publisher where ObjectType: ThreadConfined & RealmSubscribable {
  154. public typealias Output = Void
  155. public typealias Failure = Never
  156. var subscribers = [AnySubscriber<Void, Never>]()
  157. private var value: ObjectType
  158. private let keyPaths: [String]?
  159. private let unwrappedValue: ObjectBase?
  160. init(_ value: ObjectType, _ keyPaths: [String]? = nil) {
  161. self.value = value
  162. self.keyPaths = keyPaths
  163. self.unwrappedValue = nil
  164. }
  165. init(_ value: ObjectType, _ keyPaths: [String]? = nil) where ObjectType: ObjectBase {
  166. self.value = value
  167. self.keyPaths = keyPaths
  168. self.unwrappedValue = value
  169. }
  170. init(_ value: ObjectType, _ keyPaths: [String]? = nil) where ObjectType: ProjectionObservable {
  171. self.value = value
  172. self.keyPaths = keyPaths
  173. self.unwrappedValue = value.rootObject
  174. }
  175. // Refresh the publisher with a managed object.
  176. func update(value: ObjectType) {
  177. self.value = value
  178. }
  179. func send() {
  180. subscribers.forEach {
  181. _ = $0.receive()
  182. }
  183. }
  184. public func receive<S>(subscriber: S) where S: Subscriber, Failure == S.Failure, Output == S.Input {
  185. subscribers.append(AnySubscriber(subscriber))
  186. if value.realm != nil && !value.isInvalidated, let value = value.thaw() {
  187. // This path is for cases where the object is already managed. If an
  188. // unmanaged object becomes managed it will continue to use KVO.
  189. let token = value._observe(keyPaths, subscriber)
  190. subscriber.receive(subscription: ObservationSubscription(token: token))
  191. } else if let value = unwrappedValue, !value.isInvalidated {
  192. // else if the value is unmanaged
  193. let schema = ObjectSchema(RLMObjectBaseObjectSchema(value)!)
  194. let kvo = SwiftUIKVO(subscriber: subscriber)
  195. var keyPaths = [String]()
  196. for property in schema.properties {
  197. keyPaths.append(property.name)
  198. value.addObserver(kvo, forKeyPath: property.name, options: .init(), context: nil)
  199. }
  200. let subscription = SwiftUIKVO.Subscription(observer: kvo, value: value, keyPaths: keyPaths)
  201. subscriber.receive(subscription: subscription)
  202. SwiftUIKVO.observedObjects[value] = subscription
  203. }
  204. }
  205. }
  206. @available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *)
  207. private class ObservableStorage<ObservedType>: ObservableObject where ObservedType: RealmSubscribable & ThreadConfined & Equatable {
  208. @Published var value: ObservedType {
  209. willSet {
  210. if newValue != value {
  211. objectWillChange.send()
  212. objectWillChange.update(value: newValue)
  213. objectWillChange.subscribers.forEach {
  214. $0.receive(subscription: ObservationSubscription(token: newValue._observe(keyPaths, $0)))
  215. }
  216. }
  217. }
  218. }
  219. let objectWillChange: ObservableStoragePublisher<ObservedType>
  220. let keyPaths: [String]?
  221. init(_ value: ObservedType, _ keyPaths: [String]? = nil) {
  222. self.value = value.realm != nil && !value.isInvalidated ? value.thaw() ?? value : value
  223. self.objectWillChange = ObservableStoragePublisher(value, keyPaths)
  224. self.keyPaths = keyPaths
  225. }
  226. init(_ value: ObservedType, _ keyPaths: [String]? = nil) where ObservedType: ObjectBase {
  227. self.value = value.realm != nil && !value.isInvalidated ? value.thaw() ?? value : value
  228. self.objectWillChange = ObservableStoragePublisher(value, keyPaths)
  229. self.keyPaths = keyPaths
  230. }
  231. init(_ value: ObservedType, _ keyPaths: [String]? = nil) where ObservedType: ProjectionObservable {
  232. self.value = value.realm != nil && !value.isInvalidated ? value.thaw() ?? value : value
  233. self.objectWillChange = ObservableStoragePublisher(value, keyPaths)
  234. self.keyPaths = keyPaths
  235. }
  236. }
  237. @available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *)
  238. private class ObservableResultsStorage<T>: ObservableStorage<T> where T: RealmSubscribable & ThreadConfined & Equatable {
  239. private var setupHasRun = false
  240. func didSet() {
  241. if setupHasRun {
  242. updateValue()
  243. }
  244. }
  245. func updateValue() {
  246. // Implemented in subclasses
  247. fatalError()
  248. }
  249. func setupValue() {
  250. guard !setupHasRun else { return }
  251. updateValue()
  252. setupHasRun = true
  253. }
  254. var sortDescriptor: SortDescriptor? {
  255. didSet {
  256. didSet()
  257. }
  258. }
  259. var filter: NSPredicate? {
  260. didSet {
  261. didSet()
  262. }
  263. }
  264. var configuration: Realm.Configuration? {
  265. didSet {
  266. didSet()
  267. }
  268. }
  269. var searchFilter: NSPredicate? {
  270. didSet {
  271. didSet()
  272. }
  273. }
  274. private var searchString: String = ""
  275. fileprivate func searchText<T: ObjectBase>(_ text: String, on keyPath: KeyPath<T, String>) {
  276. guard text != searchString else { return }
  277. if text.isEmpty {
  278. searchFilter = nil
  279. } else {
  280. searchFilter = Query<T>()[dynamicMember: keyPath].contains(text).predicate
  281. }
  282. searchString = text
  283. }
  284. }
  285. // MARK: - StateRealmObject
  286. /// A property wrapper type that instantiates an observable object.
  287. ///
  288. /// Create a state realm object in a ``SwiftUI/View``, ``SwiftUI/App``, or
  289. /// ``SwiftUI/Scene`` by applying the `@StateRealmObject` attribute to a property
  290. /// declaration and providing an initial value that conforms to the
  291. /// <doc://com.apple.documentation/documentation/Combine/ObservableObject>
  292. /// protocol:
  293. ///
  294. /// @StateRealmObject var model = DataModel()
  295. ///
  296. /// SwiftUI creates a new instance of the object only once for each instance of
  297. /// the structure that declares the object. When published properties of the
  298. /// observable realm object change, SwiftUI updates the parts of any view that depend
  299. /// on those properties. If unmanaged, the property will be read from the object itself,
  300. /// otherwise, it will be read from the underlying Realm. Changes to the value will update
  301. /// the view asynchronously:
  302. ///
  303. /// Text(model.title) // Updates the view any time `title` changes.
  304. ///
  305. /// You can pass the state object into a property that has the
  306. /// ``SwiftUI/ObservedRealmObject`` attribute.
  307. ///
  308. /// Get a ``SwiftUI/Binding`` to one of the state object's properties using the
  309. /// `$` operator. Use a binding when you want to create a two-way connection to
  310. /// one of the object's properties. For example, you can let a
  311. /// ``SwiftUI/Toggle`` control a Boolean value called `isEnabled` stored in the
  312. /// model:
  313. ///
  314. /// Toggle("Enabled", isOn: $model.isEnabled)
  315. ///
  316. /// This will write the modified `isEnabled` property to the `model` object's Realm.
  317. @available(iOS 14.0, macOS 11.0, tvOS 14.0, watchOS 7.0, *)
  318. @propertyWrapper public struct StateRealmObject<T: RealmSubscribable & ThreadConfined & Equatable>: DynamicProperty {
  319. @available(iOS 14.0, macOS 11.0, tvOS 14.0, watchOS 7.0, *)
  320. @StateObject private var storage: ObservableStorage<T>
  321. private let defaultValue: T
  322. /// :nodoc:
  323. public var wrappedValue: T {
  324. get {
  325. let value = storage.value
  326. if value.realm == nil {
  327. // if unmanaged return the unmanaged value
  328. return value
  329. } else if value.isInvalidated {
  330. // if invalidated, return the default value
  331. return defaultValue
  332. }
  333. // else return the frozen value. the frozen value
  334. // will be consumed by SwiftUI, which requires
  335. // the ability to cache and diff objects and collections
  336. // during some timeframe. The ObjectType is frozen so that
  337. // SwiftUI can cache state. other access points will thaw
  338. // the ObjectType
  339. return value.freeze()
  340. }
  341. nonmutating set {
  342. storage.value = newValue
  343. }
  344. }
  345. /// :nodoc:
  346. public var projectedValue: Binding<T> {
  347. Binding(get: {
  348. let value = self.storage.value
  349. if value.isInvalidated {
  350. return self.defaultValue
  351. }
  352. return value
  353. }, set: { newValue in
  354. self.storage.value = newValue
  355. })
  356. }
  357. /**
  358. Initialize a RealmState struct for a given thread confined type.
  359. - parameter wrappedValue The List reference to wrap and observe.
  360. */
  361. @available(iOS 14.0, macOS 11.0, tvOS 14.0, watchOS 7.0, *)
  362. public init<Value>(wrappedValue: T) where T == List<Value> {
  363. self._storage = StateObject(wrappedValue: ObservableStorage(wrappedValue))
  364. defaultValue = T()
  365. }
  366. /**
  367. Initialize a RealmState struct for a given thread confined type.
  368. - parameter wrappedValue The MutableSet reference to wrap and observe.
  369. */
  370. @available(iOS 14.0, macOS 11.0, tvOS 14.0, watchOS 7.0, *)
  371. public init<Value>(wrappedValue: T) where T == MutableSet<Value> {
  372. self._storage = StateObject(wrappedValue: ObservableStorage(wrappedValue))
  373. defaultValue = T()
  374. }
  375. /**
  376. Initialize a RealmState struct for a given thread confined type.
  377. - parameter wrappedValue The Map reference to wrap and observe.
  378. */
  379. @available(iOS 14.0, macOS 11.0, tvOS 14.0, watchOS 7.0, *)
  380. public init<Key, Value>(wrappedValue: T) where T == Map<Key, Value> {
  381. self._storage = StateObject(wrappedValue: ObservableStorage(wrappedValue))
  382. defaultValue = T()
  383. }
  384. /**
  385. Initialize a RealmState struct for a given thread confined type.
  386. - parameter wrappedValue The ObjectBase reference to wrap and observe.
  387. */
  388. @available(iOS 14.0, macOS 11.0, tvOS 14.0, watchOS 7.0, *)
  389. public init(wrappedValue: T) where T: ObjectBase & Identifiable {
  390. self._storage = StateObject(wrappedValue: ObservableStorage(wrappedValue))
  391. defaultValue = T()
  392. }
  393. /**
  394. Initialize a RealmState struct for a given Projection type.
  395. - parameter wrappedValue The Projection reference to wrap and observe.
  396. */
  397. @available(iOS 14.0, macOS 11.0, tvOS 14.0, watchOS 7.0, *)
  398. public init(wrappedValue: T) where T: ProjectionObservable {
  399. self._storage = StateObject(wrappedValue: ObservableStorage(wrappedValue))
  400. defaultValue = T(projecting: T.Root())
  401. }
  402. /// :nodoc:
  403. public var _publisher: some Publisher {
  404. self.storage.objectWillChange
  405. }
  406. }
  407. // MARK: ObservedResults
  408. /**
  409. A type which can be used with @ObservedResults propperty wrapper. Children class of Realm Object or Projection.
  410. It's made to specialize the init methods of ObservedResults.
  411. */
  412. @available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *)
  413. public protocol _ObservedResultsValue: RealmCollectionValue { }
  414. /// :nodoc:
  415. @available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *)
  416. extension Object: _ObservedResultsValue { }
  417. /// :nodoc:
  418. @available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *)
  419. extension Projection: _ObservedResultsValue { }
  420. /// A property wrapper type that represents the results of a query on a realm.
  421. ///
  422. /// The results use the realm configuration provided by
  423. /// the environment value `EnvironmentValues/realmConfiguration`.
  424. ///
  425. /// Unlike non-SwiftUI results collections, the ObservedResults is mutable. Writes to an ObservedResults collection implicitly
  426. /// perform a write transaction. If you add an object to the ObservedResults that the associated query would filter out, the object
  427. /// is added to the realm but not included in the ObservedResults.
  428. ///
  429. /// Given `@ObservedResults var v` in SwiftUI, `$v` refers to a `BoundCollection`.
  430. ///
  431. @available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *)
  432. @propertyWrapper public struct ObservedResults<ResultType>: DynamicProperty, BoundCollection where ResultType: _ObservedResultsValue & RealmFetchable & KeypathSortable & Identifiable {
  433. public typealias Element = ResultType
  434. private class Storage: ObservableResultsStorage<Results<ResultType>> {
  435. override func updateValue() {
  436. let realm = try! Realm(configuration: configuration ?? Realm.Configuration.defaultConfiguration)
  437. var value = realm.objects(ResultType.self)
  438. if let sortDescriptor = sortDescriptor {
  439. value = value.sorted(byKeyPath: sortDescriptor.keyPath, ascending: sortDescriptor.ascending)
  440. }
  441. let filters = [searchFilter, filter].compactMap { $0 }
  442. if !filters.isEmpty {
  443. let compoundFilter = NSCompoundPredicate(andPredicateWithSubpredicates: filters)
  444. value = value.filter(compoundFilter)
  445. }
  446. self.value = value
  447. }
  448. }
  449. @Environment(\.realmConfiguration) var configuration
  450. @ObservedObject private var storage: Storage
  451. fileprivate func searchText<T: ObjectBase>(_ text: String, on keyPath: KeyPath<T, String>) {
  452. storage.searchText(text, on: keyPath)
  453. }
  454. /// Stores an NSPredicate used for filtering the Results. This is mutually exclusive
  455. /// to the `where` parameter.
  456. @State public var filter: NSPredicate? {
  457. willSet {
  458. storage.filter = newValue
  459. }
  460. }
  461. /// Stores a type safe query used for filtering the Results. This is mutually exclusive
  462. /// to the `filter` parameter.
  463. @State public var `where`: ((Query<ResultType>) -> Query<Bool>)? {
  464. willSet {
  465. storage.filter = newValue?(Query()).predicate
  466. }
  467. }
  468. /// :nodoc:
  469. @State public var sortDescriptor: SortDescriptor? {
  470. willSet {
  471. storage.sortDescriptor = newValue
  472. }
  473. }
  474. /// :nodoc:
  475. public var wrappedValue: Results<ResultType> {
  476. storage.setupValue()
  477. return storage.configuration != nil ? storage.value.freeze() : storage.value
  478. }
  479. /// :nodoc:
  480. public var projectedValue: Self {
  481. return self
  482. }
  483. /**
  484. Initialize a `ObservedResults` struct for a given `Projection` type.
  485. - parameter type: Observed type
  486. - parameter configuration: The `Realm.Configuration` used when creating the Realm,
  487. user's sync configuration for the given partition value will be set as the `syncConfiguration`,
  488. if empty the configuration is set to the `defaultConfiguration`
  489. - parameter filter: Observations will be made only for passing objects.
  490. If no filter given - all objects will be observed
  491. - parameter keyPaths: Only properties contained in the key paths array will be observed.
  492. If `nil`, notifications will be delivered for any property change on the object.
  493. String key paths which do not correspond to a valid a property will throw an exception.
  494. - parameter sortDescriptor: A sequence of `SortDescriptor`s to sort by
  495. */
  496. public init<ObjectType: ObjectBase>(_ type: ResultType.Type,
  497. configuration: Realm.Configuration? = nil,
  498. filter: NSPredicate? = nil,
  499. keyPaths: [String]? = nil,
  500. sortDescriptor: SortDescriptor? = nil) where ResultType: Projection<ObjectType>, ObjectType: ThreadConfined {
  501. let results = Results<ResultType>(RLMResults<ResultType>.emptyDetached())
  502. self.storage = Storage(results, keyPaths)
  503. self.storage.configuration = configuration
  504. self.filter = filter
  505. self.sortDescriptor = sortDescriptor
  506. }
  507. /**
  508. Initialize a `ObservedResults` struct for a given `Object` or `EmbeddedObject` type.
  509. - parameter type: Observed type
  510. - parameter configuration: The `Realm.Configuration` used when creating the Realm,
  511. user's sync configuration for the given partition value will be set as the `syncConfiguration`,
  512. if empty the configuration is set to the `defaultConfiguration`
  513. - parameter filter: Observations will be made only for passing objects.
  514. If no filter given - all objects will be observed
  515. - parameter keyPaths: Only properties contained in the key paths array will be observed.
  516. If `nil`, notifications will be delivered for any property change on the object.
  517. String key paths which do not correspond to a valid a property will throw an exception.
  518. - parameter sortDescriptor: A sequence of `SortDescriptor`s to sort by
  519. */
  520. public init(_ type: ResultType.Type,
  521. configuration: Realm.Configuration? = nil,
  522. filter: NSPredicate? = nil,
  523. keyPaths: [String]? = nil,
  524. sortDescriptor: SortDescriptor? = nil) where ResultType: Object {
  525. self.storage = Storage(Results(RLMResults<ResultType>.emptyDetached()), keyPaths)
  526. self.storage.configuration = configuration
  527. self.filter = filter
  528. self.sortDescriptor = sortDescriptor
  529. }
  530. /**
  531. Initialize a `ObservedResults` struct for a given `Object` or `EmbeddedObject` type.
  532. - parameter type: Observed type
  533. - parameter configuration: The `Realm.Configuration` used when creating the Realm,
  534. user's sync configuration for the given partition value will be set as the `syncConfiguration`,
  535. if empty the configuration is set to the `defaultConfiguration`
  536. - parameter where: Observations will be made only for passing objects.
  537. If no type safe query is given - all objects will be observed
  538. - parameter keyPaths: Only properties contained in the key paths array will be observed.
  539. If `nil`, notifications will be delivered for any property change on the object.
  540. String key paths which do not correspond to a valid a property will throw an exception.
  541. - parameter sortDescriptor: A sequence of `SortDescriptor`s to sort by
  542. */
  543. public init(_ type: ResultType.Type,
  544. configuration: Realm.Configuration? = nil,
  545. where: ((Query<ResultType>) -> Query<Bool>)? = nil,
  546. keyPaths: [String]? = nil,
  547. sortDescriptor: SortDescriptor? = nil) where ResultType: Object {
  548. self.storage = Storage(Results(RLMResults<ResultType>.emptyDetached()), keyPaths)
  549. self.storage.configuration = configuration
  550. self.where = `where`
  551. self.sortDescriptor = sortDescriptor
  552. }
  553. /// :nodoc:
  554. public init(_ type: ResultType.Type,
  555. keyPaths: [String]? = nil,
  556. configuration: Realm.Configuration? = nil,
  557. sortDescriptor: SortDescriptor? = nil) where ResultType: Object {
  558. self.storage = Storage(Results(RLMResults<ResultType>.emptyDetached()), keyPaths)
  559. self.storage.configuration = configuration
  560. self.sortDescriptor = sortDescriptor
  561. }
  562. nonisolated public func update() {
  563. assumeOnMainActorExecutor {
  564. // When the view updates, it will inject the @Environment
  565. // into the propertyWrapper
  566. if storage.configuration == nil {
  567. storage.configuration = configuration
  568. }
  569. }
  570. }
  571. }
  572. /// A property wrapper type that represents a sectioned results collection.
  573. ///
  574. /// The sectioned results use the realm configuration provided by
  575. /// the environment value `EnvironmentValues/realmConfiguration`
  576. /// if `configuration` is not set in the initializer.
  577. ///
  578. ///
  579. /// Given `@ObservedSectionedResults var v` in SwiftUI, `$v` refers to a `BoundCollection`.
  580. ///
  581. @available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *)
  582. @propertyWrapper public struct ObservedSectionedResults<Key: _Persistable & Hashable, ResultType>: DynamicProperty, BoundCollection where ResultType: _ObservedResultsValue & RealmFetchable & KeypathSortable & Identifiable {
  583. public typealias Element = ResultType
  584. private class Storage: ObservableResultsStorage<SectionedResults<Key, ResultType>> {
  585. override func updateValue() {
  586. let realm = try! Realm(configuration: configuration ?? Realm.Configuration.defaultConfiguration)
  587. var results = realm.objects(ResultType.self)
  588. let filters = [searchFilter, filter].compactMap { $0 }
  589. if !filters.isEmpty {
  590. let compoundFilter = NSCompoundPredicate(andPredicateWithSubpredicates: filters)
  591. results = results.filter(compoundFilter)
  592. }
  593. if let keyPathString = keyPathString, sortDescriptors.isEmpty {
  594. sortDescriptors.append(.init(keyPath: keyPathString, ascending: true))
  595. }
  596. value = results.sectioned(sortDescriptors: sortDescriptors, sectionBlock)
  597. }
  598. var sortDescriptors: [SortDescriptor] = [] {
  599. didSet {
  600. didSet()
  601. }
  602. }
  603. var sectionBlock: ((ResultType) -> Key)
  604. var keyPathString: String?
  605. init(_ value: Results<ResultType>,
  606. sectionBlock: @escaping ((ResultType) -> Key),
  607. sortDescriptors: [SortDescriptor],
  608. keyPathString: String? = nil,
  609. keyPaths: [String]? = nil) {
  610. self.sectionBlock = sectionBlock
  611. self.sortDescriptors = sortDescriptors
  612. if let keyPathString = keyPathString {
  613. self.keyPathString = keyPathString
  614. self.sortDescriptors.append(.init(keyPath: keyPathString, ascending: true))
  615. }
  616. if self.sortDescriptors.isEmpty {
  617. throwRealmException("sortDescriptors must not be empty when sectioning ObservedSectionedResults with `sectionBlock`")
  618. }
  619. super.init(value.sectioned(sortDescriptors: self.sortDescriptors, self.sectionBlock), keyPaths)
  620. }
  621. }
  622. @Environment(\.realmConfiguration) var configuration
  623. @ObservedObject private var storage: Storage
  624. /// :nodoc:
  625. fileprivate func searchText<T: ObjectBase>(_ text: String, on keyPath: KeyPath<T, String>) {
  626. storage.searchText(text, on: keyPath)
  627. }
  628. /// Stores an NSPredicate used for filtering the SectionedResults. This is mutually exclusive
  629. /// to the `where` parameter.
  630. @State public var filter: NSPredicate? {
  631. willSet {
  632. storage.filter = newValue
  633. }
  634. }
  635. /// Stores a type safe query used for filtering the SectionedResults. This is mutually exclusive
  636. /// to the `filter` parameter.
  637. @State public var `where`: ((Query<ResultType>) -> Query<Bool>)? {
  638. willSet {
  639. storage.filter = newValue?(Query()).predicate
  640. }
  641. }
  642. /// :nodoc:
  643. @State public var sortDescriptors: [SortDescriptor] = [] {
  644. willSet {
  645. storage.sortDescriptors = newValue
  646. }
  647. }
  648. /// :nodoc:
  649. public var wrappedValue: SectionedResults<Key, ResultType> {
  650. storage.setupValue()
  651. return storage.value
  652. }
  653. /// :nodoc:
  654. public var projectedValue: Self {
  655. return self
  656. }
  657. private init(type: ResultType.Type,
  658. sectionBlock: @escaping ((ResultType) -> Key),
  659. sortDescriptors: [SortDescriptor] = [],
  660. filter: NSPredicate? = nil,
  661. where: ((Query<ResultType>) -> Query<Bool>)? = nil,
  662. keyPaths: [String]? = nil,
  663. keyPathString: String? = nil,
  664. configuration: Realm.Configuration? = nil) where ResultType: AnyObject {
  665. let results = Results<ResultType>(RLMResults<ResultType>.emptyDetached())
  666. self.storage = Storage(results,
  667. sectionBlock: sectionBlock,
  668. sortDescriptors: sortDescriptors,
  669. keyPathString: keyPathString,
  670. keyPaths: keyPaths)
  671. self.storage.configuration = configuration
  672. if let filter = filter {
  673. self.filter = filter
  674. } else if let `where` = `where` {
  675. self.where = `where`
  676. }
  677. self.sortDescriptors = sortDescriptors
  678. }
  679. /**
  680. Initialize a `ObservedSectionedResults` struct for a given `Projection` type.
  681. - parameter type: Observed type
  682. - parameter sectionKeyPath: The keyPath that will produce the key for each section.
  683. For every unique value retrieved from the keyPath a section key will be generated.
  684. - parameter sortDescriptors: A sequence of `SortDescriptor`s to sort by.
  685. - parameter filter: Observations will be made only for passing objects.
  686. If no filter given - all objects will be observed
  687. - parameter keyPaths: Only properties contained in the key paths array will be observed.
  688. If `nil`, notifications will be delivered for any property change on the object.
  689. String key paths which do not correspond to a valid a property will throw an exception.
  690. - parameter configuration: The `Realm.Configuration` used when creating the Realm.
  691. If empty the configuration is set to the `defaultConfiguration`
  692. - note: The primary sort descriptor must be responsible for determining the section key.
  693. */
  694. public init<ObjectType: ObjectBase>(_ type: ResultType.Type,
  695. sectionKeyPath: KeyPath<ResultType, Key>,
  696. sortDescriptors: [SortDescriptor] = [],
  697. filter: NSPredicate? = nil,
  698. keyPaths: [String]? = nil,
  699. configuration: Realm.Configuration? = nil) where ResultType: Projection<ObjectType>, ObjectType: ThreadConfined {
  700. self.init(type: type,
  701. sectionBlock: { (obj: ResultType) in obj[keyPath: sectionKeyPath] },
  702. sortDescriptors: sortDescriptors,
  703. filter: filter,
  704. keyPaths: keyPaths,
  705. keyPathString: _name(for: sectionKeyPath),
  706. configuration: configuration)
  707. }
  708. /**
  709. Initialize a `ObservedSectionedResults` struct for a given `Projection` type.
  710. - parameter type: Observed type
  711. - parameter sectionBlock: A callback which returns the section key for each object in the collection.
  712. - parameter sortDescriptors: A sequence of `SortDescriptor`s to sort by.
  713. - parameter filter: Observations will be made only for passing objects.
  714. If no filter given - all objects will be observed
  715. - parameter keyPaths: Only properties contained in the key paths array will be observed.
  716. If `nil`, notifications will be delivered for any property change on the object.
  717. String key paths which do not correspond to a valid a property will throw an exception.
  718. - parameter configuration: The `Realm.Configuration` used when creating the Realm.
  719. If empty the configuration is set to the `defaultConfiguration`
  720. - note: The primary sort descriptor must be responsible for determining the section key.
  721. */
  722. public init<ObjectType: ObjectBase>(_ type: ResultType.Type,
  723. sectionBlock: @escaping ((ResultType) -> Key),
  724. sortDescriptors: [SortDescriptor] = [],
  725. filter: NSPredicate? = nil,
  726. keyPaths: [String]? = nil,
  727. configuration: Realm.Configuration? = nil) where ResultType: Projection<ObjectType>, ObjectType: ThreadConfined {
  728. self.init(type: type,
  729. sectionBlock: sectionBlock,
  730. sortDescriptors: sortDescriptors,
  731. filter: filter,
  732. keyPaths: keyPaths,
  733. configuration: configuration)
  734. }
  735. /**
  736. Initialize a `ObservedSectionedResults` struct for a given `Object` or `EmbeddedObject` type.
  737. - parameter type: Observed type
  738. - parameter sectionKeyPath: The keyPath that will produce the key for each section.
  739. For every unique value retrieved from the keyPath a section key will be generated.
  740. - parameter sortDescriptors: A sequence of `SortDescriptor`s to sort by.
  741. - parameter filter: Observations will be made only for passing objects.
  742. If no filter given - all objects will be observed
  743. - parameter keyPaths: Only properties contained in the key paths array will be observed.
  744. If `nil`, notifications will be delivered for any property change on the object.
  745. String key paths which do not correspond to a valid a property will throw an exception.
  746. - parameter configuration: The `Realm.Configuration` used when creating the Realm.
  747. If empty the configuration is set to the `defaultConfiguration`
  748. - note: The primary sort descriptor must be responsible for determining the section key.
  749. */
  750. public init(_ type: ResultType.Type,
  751. sectionKeyPath: KeyPath<ResultType, Key>,
  752. sortDescriptors: [SortDescriptor] = [],
  753. filter: NSPredicate? = nil,
  754. keyPaths: [String]? = nil,
  755. configuration: Realm.Configuration? = nil) where ResultType: Object {
  756. self.init(type: type,
  757. sectionBlock: { (obj: ResultType) in obj[keyPath: sectionKeyPath] },
  758. sortDescriptors: sortDescriptors,
  759. filter: filter,
  760. keyPaths: keyPaths,
  761. keyPathString: _name(for: sectionKeyPath),
  762. configuration: configuration)
  763. }
  764. /**
  765. Initialize a `ObservedSectionedResults` struct for a given `Object` or `EmbeddedObject` type.
  766. - parameter type: Observed type
  767. - parameter sectionBlock: A callback which returns the section key for each object in the collection.
  768. - parameter sortDescriptors: A sequence of `SortDescriptor`s to sort by.
  769. - parameter filter: Observations will be made only for passing objects.
  770. If no filter given - all objects will be observed
  771. - parameter keyPaths: Only properties contained in the key paths array will be observed.
  772. If `nil`, notifications will be delivered for any property change on the object.
  773. String key paths which do not correspond to a valid a property will throw an exception.
  774. - parameter configuration: The `Realm.Configuration` used when creating the Realm.
  775. If empty the configuration is set to the `defaultConfiguration`
  776. - note: The primary sort descriptor must be responsible for determining the section key.
  777. */
  778. public init(_ type: ResultType.Type,
  779. sectionBlock: @escaping ((ResultType) -> Key),
  780. sortDescriptors: [SortDescriptor] = [],
  781. filter: NSPredicate? = nil,
  782. keyPaths: [String]? = nil,
  783. configuration: Realm.Configuration? = nil) where ResultType: Object {
  784. self.init(type: type,
  785. sectionBlock: sectionBlock,
  786. sortDescriptors: sortDescriptors,
  787. filter: filter,
  788. keyPaths: keyPaths,
  789. configuration: configuration)
  790. }
  791. /**
  792. Initialize a `ObservedSectionedResults` struct for a given `Object` or `EmbeddedObject` type.
  793. - parameter type: Observed type
  794. - parameter sectionBlock: A callback which returns the section key for each object in the collection.
  795. - parameter sortDescriptors: A sequence of `SortDescriptor`s to sort by.
  796. - parameter where: Observations will be made only for passing objects.
  797. If no type safe query is given - all objects will be observed.
  798. - parameter keyPaths: Only properties contained in the key paths array will be observed.
  799. If `nil`, notifications will be delivered for any property change on the object.
  800. String key paths which do not correspond to a valid a property will throw an exception.
  801. - parameter configuration: The `Realm.Configuration` used when creating the Realm.
  802. If empty the configuration is set to the `defaultConfiguration`
  803. - note: The primary sort descriptor must be responsible for determining the section key.
  804. */
  805. public init(_ type: ResultType.Type,
  806. sectionBlock: @escaping ((ResultType) -> Key),
  807. sortDescriptors: [SortDescriptor] = [],
  808. where: ((Query<ResultType>) -> Query<Bool>)? = nil,
  809. keyPaths: [String]? = nil,
  810. configuration: Realm.Configuration? = nil) where ResultType: Object {
  811. self.init(type: type,
  812. sectionBlock: sectionBlock,
  813. sortDescriptors: sortDescriptors,
  814. where: `where`,
  815. keyPaths: keyPaths,
  816. configuration: configuration)
  817. }
  818. /**
  819. Initialize a `ObservedSectionedResults` struct for a given `Object` or `EmbeddedObject` type.
  820. - parameter type: Observed type
  821. - parameter sectionKeyPath: The keyPath that will produce the key for each section.
  822. For every unique value retrieved from the keyPath a section key will be generated.
  823. - parameter sortDescriptors: A sequence of `SortDescriptor`s to sort by.
  824. - parameter where: Observations will be made only for passing objects.
  825. If no type safe query is given - all objects will be observed.
  826. - parameter keyPaths: Only properties contained in the key paths array will be observed.
  827. If `nil`, notifications will be delivered for any property change on the object.
  828. String key paths which do not correspond to a valid a property will throw an exception.
  829. - parameter configuration: The `Realm.Configuration` used when creating the Realm.
  830. If empty the configuration is set to the `defaultConfiguration`
  831. - note: The primary sort descriptor must be responsible for determining the section key.
  832. */
  833. public init(_ type: ResultType.Type,
  834. sectionKeyPath: KeyPath<ResultType, Key>,
  835. sortDescriptors: [SortDescriptor] = [],
  836. where: ((Query<ResultType>) -> Query<Bool>)? = nil,
  837. keyPaths: [String]? = nil,
  838. configuration: Realm.Configuration? = nil) where ResultType: Object {
  839. self.init(type: type,
  840. sectionBlock: { (obj: ResultType) in obj[keyPath: sectionKeyPath] },
  841. sortDescriptors: sortDescriptors,
  842. where: `where`,
  843. keyPaths: keyPaths,
  844. keyPathString: _name(for: sectionKeyPath),
  845. configuration: configuration)
  846. }
  847. /**
  848. Initialize a `ObservedSectionedResults` struct for a given `Object` or `EmbeddedObject` type.
  849. - parameter type: Observed type
  850. - parameter sectionKeyPath: The keyPath that will produce the key for each section.
  851. For every unique value retrieved from the keyPath a section key will be generated.
  852. - parameter sortDescriptors: A sequence of `SortDescriptor`s to sort by.
  853. - parameter keyPaths: Only properties contained in the key paths array will be observed.
  854. If `nil`, notifications will be delivered for any property change on the object.
  855. String key paths which do not correspond to a valid a property will throw an exception.
  856. - parameter configuration: The `Realm.Configuration` used when creating the Realm.
  857. If empty the configuration is set to the `defaultConfiguration`
  858. - note: The primary sort descriptor must be responsible for determining the section key.
  859. */
  860. public init(_ type: ResultType.Type,
  861. sectionKeyPath: KeyPath<ResultType, Key>,
  862. sortDescriptors: [SortDescriptor] = [],
  863. keyPaths: [String]? = nil,
  864. configuration: Realm.Configuration? = nil) where ResultType: Object {
  865. self.init(type: type,
  866. sectionBlock: { (obj: ResultType) in obj[keyPath: sectionKeyPath] },
  867. sortDescriptors: sortDescriptors,
  868. keyPaths: keyPaths,
  869. keyPathString: _name(for: sectionKeyPath),
  870. configuration: configuration)
  871. }
  872. /**
  873. Initialize a `ObservedSectionedResults` struct for a given `Object` or `EmbeddedObject` type.
  874. - parameter type: Observed type
  875. - parameter sectionBlock: A callback which returns the section key for each object in the collection.
  876. - parameter sortDescriptors: A sequence of `SortDescriptor`s to sort by.
  877. - parameter keyPaths: Only properties contained in the key paths array will be observed.
  878. If `nil`, notifications will be delivered for any property change on the object.
  879. String key paths which do not correspond to a valid a property will throw an exception.
  880. - parameter configuration: The `Realm.Configuration` used when creating the Realm.
  881. If empty the configuration is set to the `defaultConfiguration`
  882. - note: The primary sort descriptor must be responsible for determining the section key.
  883. */
  884. public init(_ type: ResultType.Type,
  885. sectionBlock: @escaping ((ResultType) -> Key),
  886. sortDescriptors: [SortDescriptor],
  887. keyPaths: [String]? = nil,
  888. configuration: Realm.Configuration? = nil) where ResultType: Object {
  889. self.init(type: type,
  890. sectionBlock: sectionBlock,
  891. sortDescriptors: sortDescriptors,
  892. keyPaths: keyPaths,
  893. configuration: configuration)
  894. }
  895. nonisolated public func update() {
  896. assumeOnMainActorExecutor {
  897. // When the view updates, it will inject the @Environment
  898. // into the propertyWrapper
  899. if storage.configuration == nil {
  900. storage.configuration = configuration
  901. }
  902. }
  903. }
  904. }
  905. // MARK: ObservedRealmObject
  906. /// A property wrapper type that subscribes to an observable Realm `Object` or `List` and
  907. /// invalidates a view whenever the observable object changes.
  908. @available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *)
  909. @propertyWrapper public struct ObservedRealmObject<ObjectType>: DynamicProperty where ObjectType: RealmSubscribable & ThreadConfined & ObservableObject & Equatable {
  910. /// A wrapper of the underlying observable object that can create bindings to
  911. /// its properties using dynamic member lookup.
  912. @dynamicMemberLookup @frozen public struct Wrapper {
  913. /// :nodoc:
  914. public var wrappedValue: ObjectType
  915. /// Returns a binding to the resulting value of a given key path.
  916. ///
  917. /// - Parameter keyPath : A key path to a specific resulting value.
  918. /// - Returns: A new binding.
  919. public subscript<Subject>(dynamicMember keyPath: ReferenceWritableKeyPath<ObjectType, Subject>) -> Binding<Subject> {
  920. createBinding(wrappedValue, forKeyPath: keyPath)
  921. }
  922. /// Returns a binding to the resulting equatable value of a given key path.
  923. ///
  924. /// This binding's set() will only perform a write if the new value is different from the existing value.
  925. ///
  926. /// - Parameter keyPath : A key path to a specific resulting value.
  927. /// - Returns: A new binding.
  928. public subscript<Subject: Equatable>(dynamicMember keyPath: ReferenceWritableKeyPath<ObjectType, Subject>) -> Binding<Subject> {
  929. createEquatableBinding(wrappedValue, forKeyPath: keyPath)
  930. }
  931. /// Returns a binding to the resulting collection value of a given key path.
  932. ///
  933. /// - Parameter keyPath : A key path to a specific resulting value.
  934. /// - Returns: A new binding.
  935. public subscript<Subject: RLMSwiftCollectionBase & ThreadConfined>(dynamicMember keyPath: ReferenceWritableKeyPath<ObjectType, Subject>) -> Binding<Subject> {
  936. createCollectionBinding(wrappedValue, forKeyPath: keyPath)
  937. }
  938. }
  939. /// The object to observe.
  940. @ObservedObject private var storage: ObservableStorage<ObjectType>
  941. /// A default value to avoid invalidated access.
  942. private let defaultValue: ObjectType
  943. /// :nodoc:
  944. public var wrappedValue: ObjectType {
  945. get {
  946. if storage.value.realm == nil {
  947. // if unmanaged return the unmanaged value
  948. return storage.value
  949. } else if storage.value.isInvalidated {
  950. // if invalidated, return the default value
  951. return defaultValue
  952. }
  953. // else return the frozen value. the frozen value
  954. // will be consumed by SwiftUI, which requires
  955. // the ability to cache and diff objects and collections
  956. // during some timeframe. The ObjectType is frozen so that
  957. // SwiftUI can cache state. other access points will thaw
  958. // the ObjectType
  959. return storage.value.freeze()
  960. }
  961. set {
  962. storage.value = newValue
  963. }
  964. }
  965. /// :nodoc:
  966. public var projectedValue: Wrapper {
  967. return Wrapper(wrappedValue: storage.value.isInvalidated ? defaultValue : storage.value)
  968. }
  969. /**
  970. Initialize a RealmState struct for a given thread confined type.
  971. - parameter wrappedValue The RealmSubscribable value to wrap and observe.
  972. */
  973. public init(wrappedValue: ObjectType) where ObjectType: ObjectBase & Identifiable {
  974. _storage = ObservedObject(wrappedValue: ObservableStorage(wrappedValue))
  975. defaultValue = ObjectType()
  976. }
  977. /**
  978. Initialize a RealmState struct for a given thread confined type.
  979. - parameter wrappedValue The RealmSubscribable value to wrap and observe.
  980. */
  981. public init<V>(wrappedValue: ObjectType) where ObjectType == List<V> {
  982. _storage = ObservedObject(wrappedValue: ObservableStorage(wrappedValue))
  983. defaultValue = List()
  984. }
  985. /**
  986. Initialize a RealmState struct for a given thread confined type.
  987. - parameter wrappedValue The RealmSubscribable value to wrap and observe.
  988. */
  989. public init(wrappedValue: ObjectType) where ObjectType: ProjectionObservable {
  990. _storage = ObservedObject(wrappedValue: ObservableStorage(wrappedValue))
  991. defaultValue = ObjectType(projecting: ObjectType.Root())
  992. }
  993. }
  994. @available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *)
  995. extension Binding where Value: ObjectBase & ThreadConfined {
  996. /// :nodoc:
  997. public subscript<V>(dynamicMember member: ReferenceWritableKeyPath<Value, V>) -> Binding<V> where V: _Persistable {
  998. createBinding(wrappedValue, forKeyPath: member)
  999. }
  1000. /// :nodoc:
  1001. public subscript<V>(dynamicMember member: ReferenceWritableKeyPath<Value, V>) -> Binding<V> where V: _Persistable & RLMSwiftCollectionBase & ThreadConfined {
  1002. createCollectionBinding(wrappedValue, forKeyPath: member)
  1003. }
  1004. /// :nodoc:
  1005. public subscript<V>(dynamicMember member: ReferenceWritableKeyPath<Value, V>) -> Binding<V> where V: _Persistable & Equatable {
  1006. createEquatableBinding(wrappedValue, forKeyPath: member)
  1007. }
  1008. }
  1009. // MARK: - BoundCollection
  1010. /// :nodoc:
  1011. @available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *)
  1012. @preconcurrency @MainActor
  1013. public protocol BoundCollection {
  1014. /// :nodoc:
  1015. associatedtype Value
  1016. /// :nodoc:
  1017. associatedtype Element: RealmCollectionValue
  1018. /// :nodoc:
  1019. var wrappedValue: Value { get }
  1020. }
  1021. @available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *)
  1022. extension BoundCollection {
  1023. private func write(_ block: (Value) -> Void) where Value: ThreadConfined {
  1024. RealmSwift.write(wrappedValue, block)
  1025. }
  1026. }
  1027. @available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *)
  1028. public extension BoundCollection where Value: RealmCollection {
  1029. /// :nodoc:
  1030. typealias Element = Value.Element
  1031. /// :nodoc:
  1032. typealias Index = Value.Index
  1033. /// :nodoc:
  1034. typealias Indices = Value.Indices
  1035. }
  1036. @available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *)
  1037. public extension BoundCollection where Value == List<Element> {
  1038. /// :nodoc:
  1039. func remove(at index: Index) {
  1040. write { list in
  1041. list.remove(at: index)
  1042. }
  1043. }
  1044. /// :nodoc:
  1045. func remove(atOffsets offsets: IndexSet) {
  1046. write { list in
  1047. list.remove(atOffsets: offsets)
  1048. }
  1049. }
  1050. /// :nodoc:
  1051. func move(fromOffsets offsets: IndexSet, toOffset destination: Int) {
  1052. write { list in
  1053. list.move(fromOffsets: offsets, toOffset: destination)
  1054. }
  1055. }
  1056. /// :nodoc:
  1057. func append(_ value: Value.Element) {
  1058. write { list in
  1059. list.append(value)
  1060. }
  1061. }
  1062. }
  1063. @available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *)
  1064. public extension BoundCollection where Value == List<Element>, Element: ObjectBase & ThreadConfined {
  1065. /// :nodoc:
  1066. func append(_ value: Value.Element) {
  1067. write { list in
  1068. if value.realm == nil && list.realm != nil {
  1069. SwiftUIKVO.observedObjects[value]?.cancel()
  1070. }
  1071. list.append(thawObjectIfFrozen(value))
  1072. }
  1073. }
  1074. }
  1075. @available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *)
  1076. public extension BoundCollection where Value == Results<Element>, Element: ObjectBase & ThreadConfined {
  1077. /// :nodoc:
  1078. func remove(_ object: Value.Element) {
  1079. guard let thawed = object.thaw() else { return }
  1080. write { results in
  1081. if results.index(of: thawed) != nil {
  1082. results.realm?.delete(thawed)
  1083. }
  1084. }
  1085. }
  1086. /// :nodoc:
  1087. func remove(atOffsets offsets: IndexSet) {
  1088. write { results in
  1089. results.realm?.delete(Array(offsets.map { results[$0] }))
  1090. }
  1091. }
  1092. }
  1093. @available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *)
  1094. public extension BoundCollection where Value == MutableSet<Element> {
  1095. /// :nodoc:
  1096. func remove(_ element: Value.Element) {
  1097. write { mutableSet in
  1098. mutableSet.remove(element)
  1099. }
  1100. }
  1101. /// :nodoc:
  1102. func insert(_ value: Value.Element) {
  1103. write { mutableSet in
  1104. mutableSet.insert(value)
  1105. }
  1106. }
  1107. }
  1108. @available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *)
  1109. public extension BoundCollection where Value == MutableSet<Element>, Element: ObjectBase & ThreadConfined {
  1110. /// :nodoc:
  1111. func remove(_ object: Value.Element) {
  1112. write { mutableSet in
  1113. mutableSet.remove(thawObjectIfFrozen(object))
  1114. }
  1115. }
  1116. /// :nodoc:
  1117. func insert(_ value: Value.Element) {
  1118. write { mutableSet in
  1119. if value.realm == nil && mutableSet.realm != nil {
  1120. SwiftUIKVO.observedObjects[value]?.cancel()
  1121. }
  1122. mutableSet.insert(thawObjectIfFrozen(value))
  1123. }
  1124. }
  1125. }
  1126. @available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *)
  1127. public extension BoundCollection where Value == Results<Element>, Element: Object {
  1128. /// :nodoc:
  1129. func append(_ value: Value.Element) {
  1130. write { results in
  1131. if value.realm == nil && results.realm != nil {
  1132. SwiftUIKVO.observedObjects[value]?.cancel()
  1133. }
  1134. results.realm?.add(thawObjectIfFrozen(value))
  1135. }
  1136. }
  1137. }
  1138. @available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *)
  1139. public extension BoundCollection where Value == Results<Element>, Element: ProjectionObservable & ThreadConfined, Element.Root: Object {
  1140. /// :nodoc:
  1141. func append(_ value: Value.Element) {
  1142. write { results in
  1143. if value.realm == nil && results.realm != nil {
  1144. SwiftUIKVO.observedObjects[value.rootObject]?.cancel()
  1145. }
  1146. results.realm?.add(thawObjectIfFrozen(value.rootObject))
  1147. }
  1148. }
  1149. }
  1150. @available(iOS 14.0, macOS 11.0, tvOS 14.0, watchOS 7.0, *)
  1151. extension Binding: BoundCollection where Value: RealmCollection {
  1152. /// :nodoc:
  1153. public typealias Element = Value.Element
  1154. /// :nodoc:
  1155. public typealias Index = Value.Index
  1156. /// :nodoc:
  1157. public typealias Indices = Value.Indices
  1158. }
  1159. // MARK: - BoundMap
  1160. /// :nodoc:
  1161. @available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *)
  1162. public protocol BoundMap {
  1163. /// :nodoc:
  1164. associatedtype Value: RealmKeyedCollection
  1165. /// :nodoc:
  1166. var wrappedValue: Value { get }
  1167. }
  1168. /// :nodoc:
  1169. @available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *)
  1170. public extension BoundMap {
  1171. // The compiler will not allow us to assign values by subscript as the binding is a get-only
  1172. // property. To get around this we need an explicit `set` method.
  1173. /// :nodoc:
  1174. subscript( key: Value.Key) -> Value.Value? {
  1175. self.wrappedValue[key]
  1176. }
  1177. /// :nodoc:
  1178. func set(object: Value.Value?, for key: Value.Key) {
  1179. write(self.wrappedValue) { map in
  1180. var m = map
  1181. m[key] = object
  1182. }
  1183. }
  1184. }
  1185. /// :nodoc:
  1186. @available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *)
  1187. public extension BoundMap where Value.Value: ObjectBase & ThreadConfined {
  1188. /// :nodoc:
  1189. func set(object: Value.Value?, for key: Value.Key) {
  1190. // If the value is `nil` remove it from the map.
  1191. guard let value = object else {
  1192. write(self.wrappedValue) { map in
  1193. map.removeObject(for: key)
  1194. }
  1195. return
  1196. }
  1197. // if the value is unmanaged but the map is managed, we are adding this value to the realm
  1198. if value.realm == nil && self.wrappedValue.realm != nil {
  1199. SwiftUIKVO.observedObjects[value]?.cancel()
  1200. }
  1201. write(self.wrappedValue) { map in
  1202. var m = map
  1203. m[key] = thawObjectIfFrozen(value)
  1204. }
  1205. }
  1206. }
  1207. @available(iOS 14.0, macOS 11.0, tvOS 14.0, watchOS 7.0, *)
  1208. extension Binding: BoundMap where Value: RealmKeyedCollection {
  1209. }
  1210. @available(iOS 14.0, macOS 11.0, tvOS 14.0, watchOS 7.0, *)
  1211. extension Binding where Value: Object {
  1212. /// :nodoc:
  1213. public func delete() {
  1214. write(wrappedValue) { object in
  1215. object.realm?.delete(thawObjectIfFrozen(self.wrappedValue))
  1216. }
  1217. }
  1218. }
  1219. @available(iOS 14.0, macOS 11.0, tvOS 14.0, watchOS 7.0, *)
  1220. extension Binding where Value: ProjectionObservable, Value.Root: ThreadConfined {
  1221. /// :nodoc:
  1222. public func delete() {
  1223. write(wrappedValue.rootObject) { object in
  1224. object.realm?.delete(thawObjectIfFrozen(object))
  1225. }
  1226. }
  1227. }
  1228. @available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *)
  1229. extension ThreadConfined where Self: ProjectionObservable {
  1230. /**
  1231. Create a `Binding` for a given property, allowing for
  1232. automatically transacted reads and writes behind the scenes.
  1233. This is a convenience method for SwiftUI views (e.g., TextField, DatePicker)
  1234. that require a `Binding` to be passed in. SwiftUI will automatically read/write
  1235. from the binding.
  1236. - parameter keyPath The key path to the member property.
  1237. - returns A `Binding` to the member property.
  1238. */
  1239. public func bind<V: _Persistable & Equatable>(_ keyPath: ReferenceWritableKeyPath<Self, V>) -> Binding<V> {
  1240. createEquatableBinding(self, forKeyPath: keyPath)
  1241. }
  1242. /// :nodoc:
  1243. public func bind<V: _Persistable & RLMSwiftCollectionBase & ThreadConfined>(_ keyPath: ReferenceWritableKeyPath<Self, V>) -> Binding<V> {
  1244. createCollectionBinding(self, forKeyPath: keyPath)
  1245. }
  1246. }
  1247. @available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *)
  1248. extension ObservedRealmObject.Wrapper where ObjectType: ObjectBase {
  1249. /// :nodoc:
  1250. public func delete() {
  1251. write(wrappedValue) { object in
  1252. object.realm?.delete(self.wrappedValue)
  1253. }
  1254. }
  1255. }
  1256. @available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *)
  1257. extension ThreadConfined where Self: ObjectBase {
  1258. /**
  1259. Create a `Binding` for a given property, allowing for
  1260. automatically transacted reads and writes behind the scenes.
  1261. This is a convenience method for SwiftUI views (e.g., TextField, DatePicker)
  1262. that require a `Binding` to be passed in. SwiftUI will automatically read/write
  1263. from the binding.
  1264. - parameter keyPath The key path to the member property.
  1265. - returns A `Binding` to the member property.
  1266. */
  1267. public func bind<V: _Persistable & Equatable>(_ keyPath: ReferenceWritableKeyPath<Self, V>) -> Binding<V> {
  1268. createEquatableBinding(self, forKeyPath: keyPath)
  1269. }
  1270. /// :nodoc:
  1271. public func bind<V: _Persistable & RLMSwiftCollectionBase & ThreadConfined>(_ keyPath: ReferenceWritableKeyPath<Self, V>) -> Binding<V> {
  1272. createCollectionBinding(self, forKeyPath: keyPath)
  1273. }
  1274. }
  1275. private struct RealmEnvironmentKey: EnvironmentKey {
  1276. static let defaultValue = Realm.Configuration.defaultConfiguration
  1277. }
  1278. private struct PartitionValueEnvironmentKey: EnvironmentKey {
  1279. static let defaultValue: PartitionValue? = nil
  1280. }
  1281. @available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *)
  1282. extension EnvironmentValues {
  1283. /// The current `Realm.Configuration` that the view should use.
  1284. public var realmConfiguration: Realm.Configuration {
  1285. get {
  1286. return self[RealmEnvironmentKey.self]
  1287. }
  1288. set {
  1289. self[RealmEnvironmentKey.self] = newValue
  1290. }
  1291. }
  1292. /// The current `Realm` that the view should use.
  1293. public var realm: Realm {
  1294. get {
  1295. return try! Realm(configuration: self[RealmEnvironmentKey.self])
  1296. }
  1297. set {
  1298. self[RealmEnvironmentKey.self] = newValue.configuration
  1299. }
  1300. }
  1301. /// The current `PartitionValue` that the view should use.
  1302. public var partitionValue: PartitionValue? {
  1303. get {
  1304. return self[PartitionValueEnvironmentKey.self]
  1305. }
  1306. set {
  1307. self[PartitionValueEnvironmentKey.self] = newValue
  1308. }
  1309. }
  1310. }
  1311. /**
  1312. An enum representing different states from `AsyncOpen` and `AutoOpen` process
  1313. */
  1314. public enum AsyncOpenState {
  1315. /// Starting the Realm.asyncOpen process.
  1316. case connecting
  1317. /// Waiting for a user to be logged in before executing Realm.asyncOpen.
  1318. case waitingForUser
  1319. /// The Realm has been opened and is ready for use. For AsyncOpen this means that the Realm has been fully downloaded, but for AutoOpen the existing local file may have been used if the device is offline.
  1320. case open(Realm)
  1321. /// The Realm is currently being downloaded from the server.
  1322. case progress(Progress)
  1323. /// Opening the Realm failed.
  1324. case error(Error)
  1325. }
  1326. private enum AsyncOpenKind {
  1327. case asyncOpen
  1328. case autoOpen
  1329. }
  1330. @available(iOS 14.0, macOS 11.0, tvOS 14.0, watchOS 7.0, *)
  1331. private class ObservableAsyncOpenStorage: ObservableObject {
  1332. private var asyncOpenKind: AsyncOpenKind
  1333. private var app: App
  1334. var configuration: Realm.Configuration?
  1335. var partitionValue: AnyBSON?
  1336. // Tracks User State for App for Multi-User Support
  1337. enum AppState {
  1338. case loggedIn(User)
  1339. case loggedOut
  1340. }
  1341. private var appState: AppState = .loggedOut
  1342. // Cancellables
  1343. private var appCancellable = [AnyCancellable]()
  1344. private var asyncOpenCancellable = [AnyCancellable]()
  1345. @Published fileprivate var asyncOpenState: AsyncOpenState
  1346. init(asyncOpenKind: AsyncOpenKind, app: App, configuration: Realm.Configuration?, partitionValue: AnyBSON?) {
  1347. self.asyncOpenKind = asyncOpenKind
  1348. self.app = app
  1349. self.configuration = configuration
  1350. self.partitionValue = partitionValue
  1351. // Initialising the state value depending on the user status, before first rendering.
  1352. if let user = app.currentUser {
  1353. appState = .loggedIn(user)
  1354. asyncOpenState = .connecting
  1355. } else {
  1356. asyncOpenState = .waitingForUser
  1357. }
  1358. }
  1359. var setupHasRun = false
  1360. func setup() {
  1361. guard !setupHasRun else { return }
  1362. initAsyncOpen()
  1363. setupHasRun = true
  1364. }
  1365. private func initAsyncOpen() {
  1366. if case .loggedIn(let user) = appState {
  1367. // we only open the realm on initialisation if there is a user logged.
  1368. asyncOpenForUser(user)
  1369. }
  1370. // we observe the changes in the app state to check for user changes,
  1371. // we store an internal state, so we could react to those changes (user login, user change, logout).
  1372. app.objectWillChange.sink { [weak self] app in
  1373. guard let self = self else { return }
  1374. switch self.appState {
  1375. case .loggedIn(let user):
  1376. if let newUser = app.currentUser,
  1377. user != newUser {
  1378. self.appState = .loggedIn(newUser)
  1379. self.asyncOpenState = .connecting
  1380. self.asyncOpenForUser(user)
  1381. } else if app.currentUser == nil {
  1382. self.asyncOpenState = .waitingForUser
  1383. self.appState = .loggedOut
  1384. }
  1385. case .loggedOut:
  1386. if let user = app.currentUser {
  1387. self.appState = .loggedIn(user)
  1388. self.asyncOpenState = .connecting
  1389. self.asyncOpenForUser(user)
  1390. }
  1391. }
  1392. }.store(in: &appCancellable)
  1393. }
  1394. private func asyncOpenForUser(_ user: User) {
  1395. // Set the `syncConfiguration` depending if there is partition value (pbs) or not (flx).
  1396. var config: Realm.Configuration
  1397. if let partitionValue = partitionValue {
  1398. config = user.configuration(partitionValue: partitionValue, cancelAsyncOpenOnNonFatalErrors: true)
  1399. } else {
  1400. config = user.flexibleSyncConfiguration(cancelAsyncOpenOnNonFatalErrors: true)
  1401. }
  1402. // Use the user configuration by default or set configuration with the current user `syncConfiguration`'s.
  1403. if var configuration = configuration {
  1404. // We want to throw if the configuration doesn't contain a `SyncConfiguration`
  1405. guard configuration.syncConfiguration != nil else {
  1406. throwRealmException("The used configuration was not configured with sync.")
  1407. }
  1408. let userSyncConfig = config.syncConfiguration
  1409. configuration.syncConfiguration = userSyncConfig
  1410. config = configuration
  1411. }
  1412. // Cancel any current subscriptions to asyncOpen if there is one
  1413. cancelAsyncOpen()
  1414. Realm.asyncOpen(configuration: config)
  1415. .onProgressNotification { asyncProgress in
  1416. // Do not change state to progress if the realm file is already opened or there is an error
  1417. switch self.asyncOpenState {
  1418. case .connecting, .waitingForUser, .progress:
  1419. let progress = Progress(totalUnitCount: Int64(asyncProgress.transferredBytes))
  1420. progress.completedUnitCount = Int64(asyncProgress.transferredBytes)
  1421. self.asyncOpenState = .progress(progress)
  1422. default: break
  1423. }
  1424. }
  1425. .sink { completion in
  1426. if case .failure(let error) = completion {
  1427. switch self.asyncOpenKind {
  1428. case .asyncOpen:
  1429. self.asyncOpenState = .error(error)
  1430. case .autoOpen:
  1431. if let realm = try? Realm(configuration: config) {
  1432. self.asyncOpenState = .open(realm)
  1433. } else {
  1434. self.asyncOpenState = .error(error)
  1435. }
  1436. }
  1437. }
  1438. } receiveValue: { realm in
  1439. self.asyncOpenState = .open(realm)
  1440. }.store(in: &self.asyncOpenCancellable)
  1441. }
  1442. fileprivate func update(_ partitionValue: PartitionValue?, _ configuration: Realm.Configuration) {
  1443. if let partitionValue = partitionValue {
  1444. let bsonValue = AnyBSON(partitionValue: partitionValue)
  1445. if self.partitionValue != bsonValue {
  1446. self.partitionValue = bsonValue
  1447. }
  1448. }
  1449. // We don't want to use the `defaultConfiguration` from the environment, we only want to use this environment value in @AsyncOpen if is not the default one
  1450. if configuration != .defaultConfiguration, self.configuration != configuration {
  1451. if let partitionValue = configuration.syncConfiguration?.partitionValue {
  1452. self.partitionValue = partitionValue
  1453. }
  1454. self.configuration = configuration
  1455. }
  1456. }
  1457. private func cancelAsyncOpen() {
  1458. asyncOpenCancellable.forEach { $0.cancel() }
  1459. asyncOpenCancellable = []
  1460. }
  1461. func cancel() {
  1462. cancelAsyncOpen()
  1463. appCancellable.forEach { $0.cancel() }
  1464. appCancellable = []
  1465. }
  1466. // MARK: - AutoOpen & AsyncOpen Helper
  1467. class func configureApp(appId: String? = nil, timeout: UInt? = nil) -> App {
  1468. var app: App
  1469. if let appId = appId {
  1470. app = App(id: appId)
  1471. } else {
  1472. // Check if there is a singular cached app
  1473. let cachedApps = RLMApp.allApps()
  1474. if cachedApps.count > 1 {
  1475. throwRealmException("Cannot AsyncOpen the Realm because more than one appId was found. When using multiple Apps you must explicitly pass an appId to indicate which to use.")
  1476. }
  1477. guard let cachedApp = cachedApps.first else {
  1478. throwRealmException("Cannot AsyncOpen the Realm because no appId was found. You must either explicitly pass an appId or initialize an App before displaying your View.")
  1479. }
  1480. app = cachedApp
  1481. }
  1482. // Setup timeout if needed
  1483. if let timeout = timeout {
  1484. let syncTimeoutOptions = SyncTimeoutOptions()
  1485. syncTimeoutOptions.connectTimeout = timeout
  1486. app.syncManager.timeoutOptions = syncTimeoutOptions
  1487. }
  1488. return app
  1489. }
  1490. }
  1491. // MARK: - AsyncOpen
  1492. /// A property wrapper type that initiates a `Realm.asyncOpen()` for the current user which asynchronously open a Realm,
  1493. /// and notifies states for the given process
  1494. ///
  1495. /// Add AsyncOpen to your ``SwiftUI/View`` or ``SwiftUI/App``, after a user is already logged in,
  1496. /// or if a user is going to be logged in
  1497. ///
  1498. /// @AsyncOpen(appId: "app_id", partitionValue: <partition_value>) var asyncOpen
  1499. ///
  1500. /// This will immediately initiates a `Realm.asyncOpen()` operation which will perform all work needed to get the Realm to
  1501. /// a usable state. (see Realm.asyncOpen() documentation)
  1502. ///
  1503. /// This property wrapper will publish states of the current `Realm.asyncOpen()` process like progress, errors and an opened realm,
  1504. /// which can be used to update the view
  1505. ///
  1506. /// struct AsyncOpenView: View {
  1507. /// @AsyncOpen(appId: "app_id", partitionValue: <partition_value>) var asyncOpen
  1508. ///
  1509. /// var body: some View {
  1510. /// switch asyncOpen {
  1511. /// case .notOpen:
  1512. /// ProgressView()
  1513. /// case .open(let realm):
  1514. /// ListView()
  1515. /// .environment(\.realm, realm)
  1516. /// case .error(_):
  1517. /// ErrorView()
  1518. /// case .progress(let progress):
  1519. /// ProgressView(progress)
  1520. /// }
  1521. /// }
  1522. /// }
  1523. ///
  1524. /// This opened `realm` can be later injected to the view as an environment value which will be used by our property wrappers
  1525. /// to populate the view with data from the opened realm
  1526. ///
  1527. /// ListView()
  1528. /// .environment(\.realm, realm)
  1529. ///
  1530. @available(iOS 14.0, macOS 11.0, tvOS 14.0, watchOS 7.0, *)
  1531. @propertyWrapper public struct AsyncOpen: DynamicProperty {
  1532. @Environment(\.realmConfiguration) var configuration
  1533. @Environment(\.partitionValue) var partitionValue
  1534. @ObservedObject private var storage: ObservableAsyncOpenStorage
  1535. /**
  1536. A Publisher for `AsyncOpenState`, emits a state each time the asyncOpen state changes.
  1537. */
  1538. public var projectedValue: Published<AsyncOpenState>.Publisher {
  1539. storage.$asyncOpenState
  1540. }
  1541. /// :nodoc:
  1542. public var wrappedValue: AsyncOpenState {
  1543. storage.setup()
  1544. return storage.asyncOpenState
  1545. }
  1546. /**
  1547. This will cancel any notification from the property wrapper states
  1548. */
  1549. public func cancel() {
  1550. storage.cancel()
  1551. }
  1552. /**
  1553. Initialize the property wrapper
  1554. - parameter appId: The unique identifier of your Realm app, if empty or `nil` will try to retrieve latest singular cached app.
  1555. - parameter partitionValue: The `BSON` value the Realm is partitioned on.
  1556. - parameter configuration: The `Realm.Configuration` used when creating the Realm,
  1557. user's sync configuration for the given partition value will be set as the `syncConfiguration`,
  1558. if empty the user configuration will be used.
  1559. - parameter timeout: The maximum number of milliseconds to allow for a connection to
  1560. become fully established., if empty or `nil` no connection timeout is set.
  1561. */
  1562. public init<Partition>(appId: String? = nil,
  1563. partitionValue: Partition,
  1564. configuration: Realm.Configuration? = nil,
  1565. timeout: UInt? = nil) where Partition: BSON {
  1566. let app = ObservableAsyncOpenStorage.configureApp(appId: appId, timeout: timeout)
  1567. // Store property wrapper values on the storage
  1568. storage = ObservableAsyncOpenStorage(asyncOpenKind: .asyncOpen, app: app, configuration: configuration, partitionValue: AnyBSON(partitionValue))
  1569. }
  1570. /**
  1571. Initialize the property wrapper for a flexible sync configuration.
  1572. - parameter appId: The unique identifier of your Realm app, if empty or `nil` will try to retrieve latest singular cached app.
  1573. - parameter configuration: The `Realm.Configuration` used when creating the Realm,
  1574. user's sync configuration for the given partition value will be set as the `syncConfiguration`,
  1575. if empty the user configuration will be used.
  1576. - parameter timeout: The maximum number of milliseconds to allow for a connection to
  1577. become fully established., if empty or `nil` no connection timeout is set.
  1578. */
  1579. public init(appId: String? = nil,
  1580. configuration: Realm.Configuration? = nil,
  1581. timeout: UInt? = nil) {
  1582. let app = ObservableAsyncOpenStorage.configureApp(appId: appId, timeout: timeout)
  1583. // Store property wrapper values on the storage
  1584. storage = ObservableAsyncOpenStorage(asyncOpenKind: .asyncOpen, app: app, configuration: configuration, partitionValue: nil)
  1585. }
  1586. nonisolated public func update() {
  1587. assumeOnMainActorExecutor {
  1588. storage.update(partitionValue, configuration)
  1589. }
  1590. }
  1591. }
  1592. // MARK: - AutoOpen
  1593. /// `AutoOpen` will try once to asynchronously open a Realm, but in case of no internet connection will return an opened realm
  1594. /// for the given appId and partitionValue which can be used within our view.
  1595. /// Add AutoOpen to your ``SwiftUI/View`` or ``SwiftUI/App``, after a user is already logged in
  1596. /// or if a user is going to be logged in
  1597. ///
  1598. /// @AutoOpen(appId: "app_id", partitionValue: <partition_value>, timeout: 4000) var autoOpen
  1599. ///
  1600. /// This will immediately initiates a `Realm.asyncOpen()` operation which will perform all work needed to get the Realm to
  1601. /// a usable state. (see Realm.asyncOpen() documentation)
  1602. ///
  1603. /// This property wrapper will publish states of the current `Realm.asyncOpen()` process like progress, errors and an opened realm,
  1604. /// which can be used to update the view
  1605. ///
  1606. /// struct AutoOpenView: View {
  1607. /// @AutoOpen(appId: "app_id", partitionValue: <partition_value>) var autoOpen
  1608. ///
  1609. /// var body: some View {
  1610. /// switch autoOpen {
  1611. /// case .notOpen:
  1612. /// ProgressView()
  1613. /// case .open(let realm):
  1614. /// ListView()
  1615. /// .environment(\.realm, realm)
  1616. /// case .error(_):
  1617. /// ErrorView()
  1618. /// case .progress(let progress):
  1619. /// ProgressView(progress)
  1620. /// }
  1621. /// }
  1622. /// }
  1623. ///
  1624. /// This opened `realm` can be later injected to the view as an environment value which will be used by our property wrappers
  1625. /// to populate the view with data from the opened realm
  1626. ///
  1627. /// ListView()
  1628. /// .environment(\.realm, realm)
  1629. ///
  1630. /// This property wrapper behaves similar as `AsyncOpen`, and in terms of declaration and use is completely identical,
  1631. /// but with the difference of a offline-first approach.
  1632. ///
  1633. @available(iOS 14.0, macOS 11.0, tvOS 14.0, watchOS 7.0, *)
  1634. @propertyWrapper public struct AutoOpen: DynamicProperty {
  1635. @Environment(\.realmConfiguration) var configuration
  1636. @Environment(\.partitionValue) var partitionValue
  1637. @ObservedObject private var storage: ObservableAsyncOpenStorage
  1638. /**
  1639. A Publisher for `AsyncOpenState`, emits a state each time the asyncOpen state changes.
  1640. */
  1641. public var projectedValue: Published<AsyncOpenState>.Publisher {
  1642. storage.$asyncOpenState
  1643. }
  1644. /// :nodoc:
  1645. public var wrappedValue: AsyncOpenState {
  1646. storage.setup()
  1647. return storage.asyncOpenState
  1648. }
  1649. /**
  1650. This will cancel any notification from the property wrapper states
  1651. */
  1652. public func cancel() {
  1653. storage.cancel()
  1654. }
  1655. /**
  1656. Initialize the property wrapper
  1657. - parameter appId: The unique identifier of your Realm app, if empty or `nil` will try to retrieve latest singular cached app.
  1658. - parameter partitionValue: The `BSON` value the Realm is partitioned on.
  1659. - parameter configuration: The `Realm.Configuration` used when creating the Realm,
  1660. user's sync configuration for the given partition value will be set as the `syncConfiguration`,
  1661. if empty the user configuration will be used.
  1662. - parameter timeout: The maximum number of milliseconds to allow for a connection to
  1663. become fully established, if empty or `nil` no connection timeout is set.
  1664. */
  1665. public init<Partition>(appId: String? = nil,
  1666. partitionValue: Partition,
  1667. configuration: Realm.Configuration? = nil,
  1668. timeout: UInt? = nil) where Partition: BSON {
  1669. let app = ObservableAsyncOpenStorage.configureApp(appId: appId, timeout: timeout)
  1670. // Store property wrapper values on the storage
  1671. storage = ObservableAsyncOpenStorage(asyncOpenKind: .autoOpen, app: app, configuration: configuration, partitionValue: AnyBSON(partitionValue))
  1672. }
  1673. /**
  1674. Initialize the property wrapper for a flexible sync configuration.
  1675. - parameter appId: The unique identifier of your Realm app, if empty or `nil` will try to retrieve latest singular cached app.
  1676. - parameter configuration: The `Realm.Configuration` used when creating the Realm,
  1677. user's sync configuration for the given partition value will be set as the `syncConfiguration`,
  1678. if empty the user configuration will be used.
  1679. - parameter timeout: The maximum number of milliseconds to allow for a connection to
  1680. become fully established., if empty or `nil` no connection timeout is set.
  1681. */
  1682. public init(appId: String? = nil,
  1683. configuration: Realm.Configuration? = nil,
  1684. timeout: UInt? = nil) {
  1685. let app = ObservableAsyncOpenStorage.configureApp(appId: appId, timeout: timeout)
  1686. // Store property wrapper values on the storage
  1687. storage = ObservableAsyncOpenStorage(asyncOpenKind: .autoOpen, app: app, configuration: configuration, partitionValue: nil)
  1688. }
  1689. nonisolated public func update() {
  1690. assumeOnMainActorExecutor {
  1691. storage.update(partitionValue, configuration)
  1692. }
  1693. }
  1694. }
  1695. @available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *)
  1696. extension SwiftUIKVO {
  1697. @objc(removeObserversFromObject:) static func removeObservers(object: NSObject) -> Bool {
  1698. if let subscription = SwiftUIKVO.observedObjects[object] {
  1699. subscription.removeObservers()
  1700. return true
  1701. } else {
  1702. return false
  1703. }
  1704. }
  1705. @objc(addObserversToObject:) static func addObservers(object: NSObject) {
  1706. if let subscription = SwiftUIKVO.observedObjects[object] {
  1707. subscription.addObservers()
  1708. }
  1709. }
  1710. }
  1711. // Adding `_Concurrency` flag is the only way to verify
  1712. // if the BASE SDK contains latest framework updates
  1713. #if canImport(_Concurrency)
  1714. @available(iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0, *)
  1715. extension View {
  1716. /// Marks this view as searchable, which configures the display of a search field.
  1717. /// You can provide a collection and a key path to be filtered using the search
  1718. /// field string provided by the searchable component, this will result in the collection
  1719. /// querying for all items containing the search field string for the given key path.
  1720. ///
  1721. /// @State var searchString: String
  1722. /// @ObservedResults(Reminder.self) var reminders
  1723. ///
  1724. /// List {
  1725. /// ForEach(reminders) { reminder in
  1726. /// ReminderRowView(reminder: reminder)
  1727. /// }
  1728. /// }
  1729. /// .searchable(text: $searchFilter,
  1730. /// collection: $reminders,
  1731. /// keyPath: \.name) {
  1732. /// ForEach(reminders) { remindersFiltered in
  1733. /// Text(remindersFiltered.name).searchCompletion(remindersFiltered.name)
  1734. /// }
  1735. /// }
  1736. ///
  1737. /**
  1738. - Note: See ``SwiftUI/View/searchable(text:placement:prompt)``
  1739. <https://developer.apple.com/documentation/swiftui/form/searchable(text:placement:prompt:)-6royb>
  1740. for more information on searchable view modifier.
  1741. - parameter text: The text to display and edit in the search field.
  1742. - parameter collection: The collection to be filtered.
  1743. - parameter keyPath: The key path to the property which will be used to filter
  1744. the collection, only key paths with `String` type are allowed.
  1745. - parameter placement: The preferred placement of the search field within the
  1746. containing view hierarchy.
  1747. - parameter prompt: A `Text` representing the prompt of the search field
  1748. which provides users with guidance on what to search for.
  1749. */
  1750. public func searchable<T: ObjectBase>(text: Binding<String>, collection: ObservedResults<T>, keyPath: KeyPath<T, String>,
  1751. placement: SearchFieldPlacement = .automatic, prompt: Text? = nil) -> some View {
  1752. filterCollection(collection, for: text.wrappedValue, on: keyPath)
  1753. return searchable(text: text, placement: placement, prompt: prompt)
  1754. }
  1755. /// Marks this view as searchable, which configures the display of a search field.
  1756. /// You can provide a collection and a key path to be filtered using the search
  1757. /// field string provided by the searchable component, this will result in the collection
  1758. /// querying for all items containing the search field string for the given key path.
  1759. ///
  1760. /// @State var searchString: String
  1761. /// @ObservedResults(Reminder.self) var reminders
  1762. ///
  1763. /// List {
  1764. /// ForEach(reminders) { reminder in
  1765. /// ReminderRowView(reminder: reminder)
  1766. /// }
  1767. /// }
  1768. /// .searchable(text: $searchFilter,
  1769. /// collection: $reminders,
  1770. /// keyPath: \.name) {
  1771. /// ForEach(reminders) { remindersFiltered in
  1772. /// Text(remindersFiltered.name).searchCompletion(remindersFiltered.name)
  1773. /// }
  1774. /// }
  1775. ///
  1776. /**
  1777. - Note: See ``SwiftUI/View/searchable(text:placement:prompt)``
  1778. <https://developer.apple.com/documentation/swiftui/form/searchable(text:placement:prompt:)-2ed8t>
  1779. for more information on searchable view modifier.
  1780. - parameter text: The text to display and edit in the search field.
  1781. - parameter collection: The collection to be filtered.
  1782. - parameter keyPath: The key path to the property which will be used to filter
  1783. the collection.
  1784. - parameter placement: The preferred placement of the search field within the
  1785. containing view hierarchy.
  1786. - parameter prompt: The key for the localized prompt of the search field
  1787. which provides users with guidance on what to search for.
  1788. */
  1789. public func searchable<T: ObjectBase>(text: Binding<String>, collection: ObservedResults<T>,
  1790. keyPath: KeyPath<T, String>, placement: SearchFieldPlacement = .automatic,
  1791. prompt: LocalizedStringKey) -> some View {
  1792. filterCollection(collection, for: text.wrappedValue, on: keyPath)
  1793. return searchable(text: text, placement: placement, prompt: prompt)
  1794. }
  1795. /// Marks this view as searchable, which configures the display of a search field.
  1796. /// You can provide a collection and a key path to be filtered using the search
  1797. /// field string provided by the searchable component, this will result in the collection
  1798. /// querying for all items containing the search field string for the given key path.
  1799. ///
  1800. /// @State var searchString: String
  1801. /// @ObservedResults(Reminder.self) var reminders
  1802. ///
  1803. /// List {
  1804. /// ForEach(reminders) { reminder in
  1805. /// ReminderRowView(reminder: reminder)
  1806. /// }
  1807. /// }
  1808. /// .searchable(text: $searchFilter,
  1809. /// collection: $reminders,
  1810. /// keyPath: \.name) {
  1811. /// ForEach(reminders) { remindersFiltered in
  1812. /// Text(remindersFiltered.name).searchCompletion(remindersFiltered.name)
  1813. /// }
  1814. /// }
  1815. ///
  1816. /**
  1817. - Note: See ``SwiftUI/View/searchable(text:placement:prompt)``
  1818. <https://developer.apple.com/documentation/swiftui/form/searchable(text:placement:prompt:)-58egp>
  1819. for more information on searchable view modifier.
  1820. - parameter text: The text to display and edit in the search field.
  1821. - parameter collection: The collection to be filtered.
  1822. - parameter keyPath: The key path to the property which will be used to filter
  1823. the collection.
  1824. - parameter placement: The preferred placement of the search field within the
  1825. containing view hierarchy.
  1826. - parameter prompt: A string representing the prompt of the search field
  1827. which provides users with guidance on what to search for.
  1828. */
  1829. public func searchable<T: ObjectBase, S>(text: Binding<String>, collection: ObservedResults<T>, keyPath: KeyPath<T, String>,
  1830. placement: SearchFieldPlacement = .automatic, prompt: S) -> some View where S: StringProtocol {
  1831. filterCollection(collection, for: text.wrappedValue, on: keyPath)
  1832. return searchable(text: text, placement: placement, prompt: prompt)
  1833. }
  1834. /// Marks this view as searchable, which configures the display of a search field.
  1835. /// You can provide a collection and a key path to be filtered using the search
  1836. /// field string provided by the searchable component, this will result in the collection
  1837. /// querying for all items containing the search field string for the given key path.
  1838. ///
  1839. /// @State var searchString: String
  1840. /// @ObservedResults(Reminder.self) var reminders
  1841. ///
  1842. /// List {
  1843. /// ForEach(reminders) { reminder in
  1844. /// ReminderRowView(reminder: reminder)
  1845. /// }
  1846. /// }
  1847. /// .searchable(text: $searchFilter,
  1848. /// collection: $reminders,
  1849. /// keyPath: \.name) {
  1850. /// ForEach(reminders) { remindersFiltered in
  1851. /// Text(remindersFiltered.name).searchCompletion(remindersFiltered.name)
  1852. /// }
  1853. /// }
  1854. ///
  1855. /**
  1856. - Note: See ``SwiftUI/View/searchable(text:placement:prompt:suggestions)``
  1857. <https://developer.apple.com/documentation/swiftui/form/searchable(text:placement:prompt:suggestions:)-94bdu>
  1858. for more information on searchable view modifier.
  1859. - parameter text: The text to display and edit in the search field.
  1860. - parameter collection: The collection to be filtered.
  1861. - parameter keyPath: The key path to the property which will be used to filter
  1862. the collection.
  1863. - parameter placement: The preferred placement of the search field within the
  1864. containing view hierarchy.
  1865. - parameter prompt: A `Text` representing the prompt of the search field
  1866. which provides users with guidance on what to search for.
  1867. - parameter suggestions: A view builder that produces content that
  1868. populates a list of suggestions.
  1869. */
  1870. public func searchable<T: ObjectBase, S>(text: Binding<String>, collection: ObservedResults<T>, keyPath: KeyPath<T, String>,
  1871. placement: SearchFieldPlacement = .automatic, prompt: Text? = nil, @ViewBuilder suggestions: () -> S)
  1872. -> some View where S: View {
  1873. filterCollection(collection, for: text.wrappedValue, on: keyPath)
  1874. return searchable(text: text,
  1875. placement: placement,
  1876. prompt: prompt,
  1877. suggestions: suggestions)
  1878. }
  1879. /// Marks this view as searchable, which configures the display of a search field.
  1880. /// You can provide a collection and a key path to be filtered using the search
  1881. /// field string provided by the searchable component, this will result in the collection
  1882. /// querying for all items containing the search field string for the given key path.
  1883. ///
  1884. /// @State var searchString: String
  1885. /// @ObservedResults(Reminder.self) var reminders
  1886. ///
  1887. /// List {
  1888. /// ForEach(reminders) { reminder in
  1889. /// ReminderRowView(reminder: reminder)
  1890. /// }
  1891. /// }
  1892. /// .searchable(text: $searchFilter,
  1893. /// collection: $reminders,
  1894. /// keyPath: \.name) {
  1895. /// ForEach(reminders) { remindersFiltered in
  1896. /// Text(remindersFiltered.name).searchCompletion(remindersFiltered.name)
  1897. /// }
  1898. /// }
  1899. ///
  1900. /**
  1901. - Note: See ``SwiftUI/View/searchable(text:placement:prompt:suggestions)``
  1902. <https://developer.apple.com/documentation/swiftui/form/searchable(text:placement:prompt:suggestions:)-1mw1m>
  1903. for more information on searchable view modifier.
  1904. - parameter text: The text to display and edit in the search field.
  1905. - parameter collection: The collection to be filtered.
  1906. - parameter keyPath: The key path to the property which will be used to filter
  1907. the collection.
  1908. - parameter placement: The preferred placement of the search field within the
  1909. containing view hierarchy.
  1910. - parameter prompt: The key for the localized prompt of the search field
  1911. which provides users with guidance on what to search for.
  1912. - parameter suggestions: A view builder that produces content that
  1913. populates a list of suggestions.
  1914. */
  1915. public func searchable<T: ObjectBase, S>(text: Binding<String>, collection: ObservedResults<T>, keyPath: KeyPath<T, String>,
  1916. placement: SearchFieldPlacement = .automatic, prompt: LocalizedStringKey, @ViewBuilder suggestions: () -> S)
  1917. -> some View where S: View {
  1918. filterCollection(collection, for: text.wrappedValue, on: keyPath)
  1919. return searchable(text: text,
  1920. placement: placement,
  1921. prompt: prompt,
  1922. suggestions: suggestions)
  1923. }
  1924. /// Marks this view as searchable, which configures the display of a search field.
  1925. /// You can provide a collection and a key path to be filtered using the search
  1926. /// field string provided by the searchable component, this will result in the collection
  1927. /// querying for all items containing the search field string for the given key path.
  1928. ///
  1929. /// @State var searchString: String
  1930. /// @ObservedResults(Reminder.self) var reminders
  1931. ///
  1932. /// List {
  1933. /// ForEach(reminders) { reminder in
  1934. /// ReminderRowView(reminder: reminder)
  1935. /// }
  1936. /// }
  1937. /// .searchable(text: $searchFilter,
  1938. /// collection: $reminders,
  1939. /// keyPath: \.name) {
  1940. /// ForEach(reminders) { remindersFiltered in
  1941. /// Text(remindersFiltered.name).searchCompletion(remindersFiltered.name)
  1942. /// }
  1943. /// }
  1944. ///
  1945. /**
  1946. - Note: See ``SwiftUI/View/searchable(text:placement:prompt:suggestions)``
  1947. <https://developer.apple.com/documentation/swiftui/form/searchable(text:placement:prompt:suggestions:)-6h6qo>
  1948. for more information on searchable view modifier.
  1949. - parameter text: The text to display and edit in the search field.
  1950. - parameter collection: The collection to be filtered.
  1951. - parameter keyPath: The key path to the property which will be used to filter
  1952. the collection.
  1953. - parameter placement: The preferred placement of the search field within the
  1954. containing view hierarchy.
  1955. - parameter prompt: A string representing the prompt of the search field
  1956. which provides users with guidance on what to search for.
  1957. - parameter suggestions: A view builder that produces content that
  1958. populates a list of suggestions.
  1959. */
  1960. public func searchable<T: ObjectBase, V, S>(text: Binding<String>, collection: ObservedResults<T>, keyPath: KeyPath<T, String>,
  1961. placement: SearchFieldPlacement = .automatic, prompt: S, @ViewBuilder suggestions: () -> V)
  1962. -> some View where V: View, S: StringProtocol {
  1963. filterCollection(collection, for: text.wrappedValue, on: keyPath)
  1964. return searchable(text: text,
  1965. placement: placement,
  1966. prompt: prompt,
  1967. suggestions: suggestions)
  1968. }
  1969. private func filterCollection<T: ObjectBase>(_ collection: ObservedResults<T>, for text: String, on keyPath: KeyPath<T, String>) {
  1970. assumeOnMainActorExecutor {
  1971. collection.searchText(text, on: keyPath)
  1972. }
  1973. }
  1974. /// Marks this view as searchable, which configures the display of a search field.
  1975. /// You can provide a collection and a key path to be filtered using the search
  1976. /// field string provided by the searchable component, this will result in the collection
  1977. /// querying for all items containing the search field string for the given key path.
  1978. ///
  1979. /// @State var searchString: String
  1980. /// @ObservedSectionedResults(Reminder.self) var reminders
  1981. ///
  1982. /// List {
  1983. /// ForEach(reminders) { reminderSection in
  1984. /// Section(reminderSection.key) {
  1985. /// ForEach(reminderSection) { object in
  1986. /// ReminderRowView(reminder: object)
  1987. /// }
  1988. /// }
  1989. /// }
  1990. /// }
  1991. /// .searchable(text: $searchFilter,
  1992. /// collection: $reminders,
  1993. /// keyPath: \.name) {
  1994. /// ForEach(reminders) { remindersFiltered in
  1995. /// Text(remindersFiltered.name).searchCompletion(remindersFiltered.name)
  1996. /// }
  1997. /// }
  1998. ///
  1999. /**
  2000. - Note: See ``SwiftUI/View/searchable(text:placement:prompt)``
  2001. <https://developer.apple.com/documentation/swiftui/form/searchable(text:placement:prompt:)-6royb>
  2002. for more information on searchable view modifier.
  2003. - parameter text: The text to display and edit in the search field.
  2004. - parameter collection: The collection to be filtered.
  2005. - parameter keyPath: The key path to the property which will be used to filter
  2006. the collection, only key paths with `String` type are allowed.
  2007. - parameter placement: The preferred placement of the search field within the
  2008. containing view hierarchy.
  2009. - parameter prompt: A `Text` representing the prompt of the search field
  2010. which provides users with guidance on what to search for.
  2011. */
  2012. public func searchable<Key, T: ObjectBase>(text: Binding<String>, collection: ObservedSectionedResults<Key, T>, keyPath: KeyPath<T, String>,
  2013. placement: SearchFieldPlacement = .automatic, prompt: Text? = nil) -> some View {
  2014. filterCollection(collection, for: text.wrappedValue, on: keyPath)
  2015. return searchable(text: text, placement: placement, prompt: prompt)
  2016. }
  2017. /// Marks this view as searchable, which configures the display of a search field.
  2018. /// You can provide a collection and a key path to be filtered using the search
  2019. /// field string provided by the searchable component, this will result in the collection
  2020. /// querying for all items containing the search field string for the given key path.
  2021. ///
  2022. /// @State var searchString: String
  2023. /// @ObservedResults(Reminder.self) var reminders
  2024. ///
  2025. /// List {
  2026. /// ForEach(reminders) { reminderSection in
  2027. /// Section(reminderSection.key) {
  2028. /// ForEach(reminderSection) { object in
  2029. /// ReminderRowView(reminder: object)
  2030. /// }
  2031. /// }
  2032. /// }
  2033. /// }
  2034. /// .searchable(text: $searchFilter,
  2035. /// collection: $reminders,
  2036. /// keyPath: \.name) {
  2037. /// ForEach(reminders) { remindersFiltered in
  2038. /// Text(remindersFiltered.name).searchCompletion(remindersFiltered.name)
  2039. /// }
  2040. /// }
  2041. ///
  2042. /**
  2043. - Note: See ``SwiftUI/View/searchable(text:placement:prompt)``
  2044. <https://developer.apple.com/documentation/swiftui/form/searchable(text:placement:prompt:)-2ed8t>
  2045. for more information on searchable view modifier.
  2046. - parameter text: The text to display and edit in the search field.
  2047. - parameter collection: The collection to be filtered.
  2048. - parameter keyPath: The key path to the property which will be used to filter
  2049. the collection.
  2050. - parameter placement: The preferred placement of the search field within the
  2051. containing view hierarchy.
  2052. - parameter prompt: The key for the localized prompt of the search field
  2053. which provides users with guidance on what to search for.
  2054. */
  2055. public func searchable<Key, T: ObjectBase>(text: Binding<String>, collection: ObservedSectionedResults<Key, T>,
  2056. keyPath: KeyPath<T, String>, placement: SearchFieldPlacement = .automatic,
  2057. prompt: LocalizedStringKey) -> some View {
  2058. filterCollection(collection, for: text.wrappedValue, on: keyPath)
  2059. return searchable(text: text, placement: placement, prompt: prompt)
  2060. }
  2061. /// Marks this view as searchable, which configures the display of a search field.
  2062. /// You can provide a collection and a key path to be filtered using the search
  2063. /// field string provided by the searchable component, this will result in the collection
  2064. /// querying for all items containing the search field string for the given key path.
  2065. ///
  2066. /// @State var searchString: String
  2067. /// @ObservedResults(Reminder.self) var reminders
  2068. ///
  2069. /// List {
  2070. /// ForEach(reminders) { reminderSection in
  2071. /// Section(reminderSection.key) {
  2072. /// ForEach(reminderSection) { object in
  2073. /// ReminderRowView(reminder: object)
  2074. /// }
  2075. /// }
  2076. /// }
  2077. /// }
  2078. /// .searchable(text: $searchFilter,
  2079. /// collection: $reminders,
  2080. /// keyPath: \.name) {
  2081. /// ForEach(reminders) { remindersFiltered in
  2082. /// Text(remindersFiltered.name).searchCompletion(remindersFiltered.name)
  2083. /// }
  2084. /// }
  2085. ///
  2086. /**
  2087. - Note: See ``SwiftUI/View/searchable(text:placement:prompt)``
  2088. <https://developer.apple.com/documentation/swiftui/form/searchable(text:placement:prompt:)-58egp>
  2089. for more information on searchable view modifier.
  2090. - parameter text: The text to display and edit in the search field.
  2091. - parameter collection: The collection to be filtered.
  2092. - parameter keyPath: The key path to the property which will be used to filter
  2093. the collection.
  2094. - parameter placement: The preferred placement of the search field within the
  2095. containing view hierarchy.
  2096. - parameter prompt: A string representing the prompt of the search field
  2097. which provides users with guidance on what to search for.
  2098. */
  2099. public func searchable<Key, T: ObjectBase, S>(text: Binding<String>, collection: ObservedSectionedResults<Key, T>, keyPath: KeyPath<T, String>,
  2100. placement: SearchFieldPlacement = .automatic, prompt: S) -> some View where S: StringProtocol {
  2101. filterCollection(collection, for: text.wrappedValue, on: keyPath)
  2102. return searchable(text: text, placement: placement, prompt: prompt)
  2103. }
  2104. /// Marks this view as searchable, which configures the display of a search field.
  2105. /// You can provide a collection and a key path to be filtered using the search
  2106. /// field string provided by the searchable component, this will result in the collection
  2107. /// querying for all items containing the search field string for the given key path.
  2108. ///
  2109. /// @State var searchString: String
  2110. /// @ObservedResults(Reminder.self) var reminders
  2111. ///
  2112. /// List {
  2113. /// ForEach(reminders) { reminderSection in
  2114. /// Section(reminderSection.key) {
  2115. /// ForEach(reminderSection) { object in
  2116. /// ReminderRowView(reminder: object)
  2117. /// }
  2118. /// }
  2119. /// }
  2120. /// }
  2121. /// .searchable(text: $searchFilter,
  2122. /// collection: $reminders,
  2123. /// keyPath: \.name) {
  2124. /// ForEach(reminders) { remindersFiltered in
  2125. /// Text(remindersFiltered.name).searchCompletion(remindersFiltered.name)
  2126. /// }
  2127. /// }
  2128. ///
  2129. /**
  2130. - Note: See ``SwiftUI/View/searchable(text:placement:prompt:suggestions)``
  2131. <https://developer.apple.com/documentation/swiftui/form/searchable(text:placement:prompt:suggestions:)-94bdu>
  2132. for more information on searchable view modifier.
  2133. - parameter text: The text to display and edit in the search field.
  2134. - parameter collection: The collection to be filtered.
  2135. - parameter keyPath: The key path to the property which will be used to filter
  2136. the collection.
  2137. - parameter placement: The preferred placement of the search field within the
  2138. containing view hierarchy.
  2139. - parameter prompt: A `Text` representing the prompt of the search field
  2140. which provides users with guidance on what to search for.
  2141. - parameter suggestions: A view builder that produces content that
  2142. populates a list of suggestions.
  2143. */
  2144. public func searchable<Key, T: ObjectBase, S>(text: Binding<String>, collection: ObservedSectionedResults<Key, T>, keyPath: KeyPath<T, String>,
  2145. placement: SearchFieldPlacement = .automatic, prompt: Text? = nil, @ViewBuilder suggestions: () -> S)
  2146. -> some View where S: View {
  2147. filterCollection(collection, for: text.wrappedValue, on: keyPath)
  2148. return searchable(text: text,
  2149. placement: placement,
  2150. prompt: prompt,
  2151. suggestions: suggestions)
  2152. }
  2153. /// Marks this view as searchable, which configures the display of a search field.
  2154. /// You can provide a collection and a key path to be filtered using the search
  2155. /// field string provided by the searchable component, this will result in the collection
  2156. /// querying for all items containing the search field string for the given key path.
  2157. ///
  2158. /// @State var searchString: String
  2159. /// @ObservedResults(Reminder.self) var reminders
  2160. ///
  2161. /// List {
  2162. /// ForEach(reminders) { reminderSection in
  2163. /// Section(reminderSection.key) {
  2164. /// ForEach(reminderSection) { object in
  2165. /// ReminderRowView(reminder: object)
  2166. /// }
  2167. /// }
  2168. /// }
  2169. /// }
  2170. /// .searchable(text: $searchFilter,
  2171. /// collection: $reminders,
  2172. /// keyPath: \.name) {
  2173. /// ForEach(reminders) { remindersFiltered in
  2174. /// Text(remindersFiltered.name).searchCompletion(remindersFiltered.name)
  2175. /// }
  2176. /// }
  2177. ///
  2178. /**
  2179. - Note: See ``SwiftUI/View/searchable(text:placement:prompt:suggestions)``
  2180. <https://developer.apple.com/documentation/swiftui/form/searchable(text:placement:prompt:suggestions:)-1mw1m>
  2181. for more information on searchable view modifier.
  2182. - parameter text: The text to display and edit in the search field.
  2183. - parameter collection: The collection to be filtered.
  2184. - parameter keyPath: The key path to the property which will be used to filter
  2185. the collection.
  2186. - parameter placement: The preferred placement of the search field within the
  2187. containing view hierarchy.
  2188. - parameter prompt: The key for the localized prompt of the search field
  2189. which provides users with guidance on what to search for.
  2190. - parameter suggestions: A view builder that produces content that
  2191. populates a list of suggestions.
  2192. */
  2193. public func searchable<Key, T: ObjectBase, S>(text: Binding<String>, collection: ObservedSectionedResults<Key, T>, keyPath: KeyPath<T, String>,
  2194. placement: SearchFieldPlacement = .automatic, prompt: LocalizedStringKey, @ViewBuilder suggestions: () -> S)
  2195. -> some View where S: View {
  2196. filterCollection(collection, for: text.wrappedValue, on: keyPath)
  2197. return searchable(text: text,
  2198. placement: placement,
  2199. prompt: prompt,
  2200. suggestions: suggestions)
  2201. }
  2202. /// Marks this view as searchable, which configures the display of a search field.
  2203. /// You can provide a collection and a key path to be filtered using the search
  2204. /// field string provided by the searchable component, this will result in the collection
  2205. /// querying for all items containing the search field string for the given key path.
  2206. ///
  2207. /// @State var searchString: String
  2208. /// @ObservedResults(Reminder.self) var reminders
  2209. ///
  2210. /// List {
  2211. /// ForEach(reminders) { reminderSection in
  2212. /// Section(reminderSection.key) {
  2213. /// ForEach(reminderSection) { object in
  2214. /// ReminderRowView(reminder: object)
  2215. /// }
  2216. /// }
  2217. /// }
  2218. /// }
  2219. /// .searchable(text: $searchFilter,
  2220. /// collection: $reminders,
  2221. /// keyPath: \.name) {
  2222. /// ForEach(reminders) { remindersFiltered in
  2223. /// Text(remindersFiltered.name).searchCompletion(remindersFiltered.name)
  2224. /// }
  2225. /// }
  2226. ///
  2227. /**
  2228. - Note: See ``SwiftUI/View/searchable(text:placement:prompt:suggestions)``
  2229. <https://developer.apple.com/documentation/swiftui/form/searchable(text:placement:prompt:suggestions:)-6h6qo>
  2230. for more information on searchable view modifier.
  2231. - parameter text: The text to display and edit in the search field.
  2232. - parameter collection: The collection to be filtered.
  2233. - parameter keyPath: The key path to the property which will be used to filter
  2234. the collection.
  2235. - parameter placement: The preferred placement of the search field within the
  2236. containing view hierarchy.
  2237. - parameter prompt: A string representing the prompt of the search field
  2238. which provides users with guidance on what to search for.
  2239. - parameter suggestions: A view builder that produces content that
  2240. populates a list of suggestions.
  2241. */
  2242. public func searchable<Key, T: ObjectBase, V, S>(text: Binding<String>, collection: ObservedSectionedResults<Key, T>, keyPath: KeyPath<T, String>,
  2243. placement: SearchFieldPlacement = .automatic, prompt: S, @ViewBuilder suggestions: () -> V)
  2244. -> some View where V: View, S: StringProtocol {
  2245. filterCollection(collection, for: text.wrappedValue, on: keyPath)
  2246. return searchable(text: text,
  2247. placement: placement,
  2248. prompt: prompt,
  2249. suggestions: suggestions)
  2250. }
  2251. private func filterCollection<Key, T: ObjectBase>(_ collection: ObservedSectionedResults<Key, T>, for text: String, on keyPath: KeyPath<T, String>) {
  2252. assumeOnMainActorExecutor {
  2253. collection.searchText(text, on: keyPath)
  2254. }
  2255. }
  2256. }
  2257. #endif
  2258. #else
  2259. @objc(RLMSwiftUIKVO) internal final class SwiftUIKVO: NSObject {
  2260. @objc(removeObserversFromObject:) public static func removeObservers(object: NSObject) -> Bool {
  2261. return false
  2262. }
  2263. @objc(addObserversToObject:) public static func addObservers(object: NSObject) {
  2264. }
  2265. }
  2266. #endif