Post

Replies

Boosts

Views

Activity

Reply to Why @Bindable and not @Binding in SwiftUI for iOS 17
Ok. This explanation helps more with where your thinking's at. First of all, after doing some testing, you are correct in saying @State and @Binding now work with reference types. This seems a little strange to me as in the earlier betas I tried this and views weren't being updated (what I thought should happen). So maybe something has changed since then, I'm not sure. It does however make @Bindable sort of in a strange place as bindings can just be extracted from State or Binding. Hopefully, the documentation can be updated to show what property wrapper needs to be used for what, because at the moment it seems like they all do similar things now. ‎Secondly: and just changed the name of @ObservedObject into @Bindable This isn't technically true. @ObservedObject was needed regardless in order for SwiftUI to watch the model object's properties for changes. Now SwiftUI can watch for changes without a property wrapper, and more efficiently too: only when properties that are accessed change will the view be reloaded. @Bindable is more of an attachment to the model allowing binding creation. I don't think it does anything regarding observation itself.
Topic: UI Frameworks SubTopic: SwiftUI Tags:
Aug ’23
Reply to Why @Bindable and not @Binding in SwiftUI for iOS 17
I think you're getting confused with all the property wrappers now that iOS 17 and the Observation framework have been introduced. I can give you a quick run down of what each of them do now. ‎ State Use to create a single source of truth for a given value type in a view. Shouldn't be used for reference types as SwiftUI won't update the view when one of its properties changes. @State private var isFavorite = false // Bool is a value type Toggle("Favorite", isOn: $isFavorite) // access the Binding value through $ prefix ‎ Binding Use to create a two-way connection between a property that stores data and a view that displays and changes the data. It connects a property to a source of truth stored elsewhere instead of storing data directly. @Binding var isFavorite: Bool Toggle("Favorite", isOn: $isFavorite) // access the underlying Binding value through $ prefix Would be passed in from parent like so: @State private var isFavorite = false ToggleView(isFavorite: $isFavorite) // expects a Binding so give it one using the $ prefix ‎ Environment Use to read a value stored in a view's environment: An EnvironmentValues environment value @Environment(\.colorScheme) private var colorScheme: ColorScheme A type conforming to the Observable protocol. @Observable final class Profile { var name: String init(name: String) { self.name = name } } @Environment(Profile.self) private var profile: Profile ‎ Bindable Use to create bindings to mutable properties of a data model object that conforms to the Observable protocol. // Can be applied to an initialised new instance @Bindable private var profile = Profile(name: "John Doe") // Can be applied to a parameter property // This acts as a standard property and the value is passed as an argument as normal @Bindable var profile: Profile TextField("Name", text: $profile.name) // Binding object can be extracted with the help of Bindable Can also be used in conjunction with Environment: @Environment(Profile.self) private var profile: Profile @Bindable var profile = profile // must be placed in view body TextField("Name", text: $profile.name) // Binding object can be extracted with the help of Bindable ‎ No Property Wrapper Use for constant properties or similar let viewWidth = 300.0 Use for storing an instance of a type conforming to the Observable protocol. // Can be as an initialisation of a new instance let profile = Profile(name: "John Doe") // Can be as a parameter property let profile: Profile SwiftUI will automatically update the view body when any of the observed values changes. No need for a property wrapper here: it's all done behind the scenes. So, to answer your original question, Binding and Bindable are two completely different property wrappers used for different purposes. Binding is used for reading and writing a value owned by a source of truth, whereas Bindable is used only to create bindings from the properties of observable objects. You can check out the documentation for more information: State Binding Environment Bindable and also watch the WWDC23 session video Discover Observation in SwiftUI.
Topic: UI Frameworks SubTopic: SwiftUI Tags:
Aug ’23
Reply to Pass a styling as a parameter of a @ViewBuilder function, how?
I tried your code with some test examples but I got a different error. However, it was related to the fact the the parameter of the closure in EnumToShape is of type any HasNormalizedAABB. SwiftUI cannot work with that as it's not a specific type of view: it's any kind of view. A workaround, or solution, would be to create a new wrapper type that conforms to HasNormalizedAABB, and hence View, that can wrap any kind of HasNormalizedAABB. Here is an example implementation: struct AnyHasNormalizedAABB: HasNormalizedAABB { private let wrapped: any HasNormalizedAABB init(_ wrapped: any HasNormalizedAABB) { self.wrapped = wrapped } func normalizedAABB() -> CGRect { wrapped.normalizedAABB() } func path(in rect: CGRect) -> Path { wrapped.path(in: rect) } } @ViewBuilder func EnumToShape( enumState: VisibleShape, transform: @escaping (AnyHasNormalizedAABB) -> some View ) -> some View { switch enumState { case .A: transform(.init(ShapeA())) case .B: transform(.init(ShapeB())) case .C: transform(.init(ShapeC())) case .D: transform(.init(ShapeD())) case .E: transform(.init(ShapeE())) case .F: transform(.init(ShapeF())) } }
Topic: UI Frameworks SubTopic: SwiftUI Tags:
Jul ’23
Reply to Drawing a Pie without Path?
You can override the default Animatable implementation for Shape and provide your own for your custom pie slice. One small problem, however, is that Animatable requires the value(s) to be animated conform to VectorArithmetic. You are using the Angle type which does not conform. You can either add the conformance yourself, or use a type that does conform, such as Double. Here is an example that animates changes to the pie slice correctly: struct ContentView: View { @State private var amount = 0.0 var body: some View { PieSlice(start: -90, end: amount) .fill(.pink) .onTapGesture { withAnimation { amount += 30 } } } } struct PieSlice: Shape { var start: Double var end: Double var animatableData: AnimatablePair<Double, Double> { get { .init(start, end) } set { start = newValue.first end = newValue.second } } func path(in rect: CGRect) -> Path { var path = Path() let center = CGPoint(x: rect.midX, y: rect.midY) path.move(to: center) path.addArc(center: center, radius: rect.midX, startAngle: .degrees(start), endAngle: .degrees(end), clockwise: false) return path } }
Topic: UI Frameworks SubTopic: SwiftUI Tags:
Jul ’23
Reply to WebView UIViewRepresentable navigationDelegate not getting called
You are setting the web view's navigationDelegate property from within one of the delegate's own methods. The delegate can't call that function because it hasn't been assigned to and doesn't know your implementation exists. Assign your delegates when you create the view, like in the makeUIView(context:) method. func makeUIView(context: Context) -> WKWebView { let webView = WKWebView() webView.uiDelegate = context.coordinator webView.navigationDelegate = context.coordinator return webView } Now the delegate knows the functions to call and you should see your print statement appear in the console.
Topic: UI Frameworks SubTopic: SwiftUI Tags:
Jul ’23
Reply to NavigationPath doesn't work with the @Observable macro iOS 17
With the new Observation APIs in SwiftUI, you no longer need to use @StateObject to initialise the observable objects. You also don't need to use @State like you are doing. You can simply do this: let navigationModel = NavigationModel() The Observable macro does the rest. However, the NavigationStack requires a binding to a path and your navigationModel object cannot provide one. There is a solution and that is through the @Bindable property wrapper, used like this: @Bindable var navigationModel = NavigationModel() Now you can get bindings from properties within that object.
Topic: UI Frameworks SubTopic: SwiftUI Tags:
Jul ’23
Reply to SwiftData Query relationships not working
The problem is with your predicate. let filter = #Predicate<Student> { student in student.department?.id == departmentID // <- here } You are comparing two UUID properties and, according to the beta 2 release notes, it doesn't like this. SwiftData Known Issues • #Predicate does not support UUID, Date, and URL properties. (109539652)
Topic: UI Frameworks SubTopic: SwiftUI Tags:
Jul ’23
Reply to How to form a search predicate using swift data with a one to many model
My initial thought for this was to use something similar to @SectionedFetchRequest: get all the articles, filter them down, then group by section. At the moment SwiftData doesn't have that capability. I did file feedback for this (FB12292770) so hopefully this is possible later on and could be a solution for your problem. Sectioning would be more efficient because you are currently filtering twice, one for the sections' articles and then again for the articles in each section, when you could do it all at once including a predicate. ‎ "No exact matches in reference to static method 'buildExpression'" This normally means that there is an error somewhere else in the view code but you just need to find it. Try commenting out certain bits to see where the actual error is located. My guess, by looking at your code, is that you are applying the .listRowSeparator(.hidden) to the if statement and not an actual view. ‎ I have noted that you are force unwrapping result.toArticles!. Maybe you could remove the optional and give it a default value instead if you are confident it won't be nil. var toArticles: [ArticlesSD] = [] This might eliminate some of the optionality problems encountered with the predicate.
Topic: UI Frameworks SubTopic: SwiftUI Tags:
Jun ’23
Reply to Can't figure out why nothing conforms to "View"
Xcode/Swift like sending you in different directions with errors messages, but this one I'm sure is easier to solve. The issue is not with the ToolbarItemGroup but with the Label in the ToolbarItem. ToolbarItem(placement: .principal) { Label("hi") // error is here } Label requires two arguments and you have only passed it one. Either change it to a Text view or provide the second parameter, either systemImage: or image:.
Topic: UI Frameworks SubTopic: SwiftUI Tags:
Jun ’23
Reply to How to form a search predicate using swift data with a one to many model
Thank you for this. I decided to check whether the logic of the predicate expression was wrong or the #Predicate itself was doing something, and it turns out it's the latter. Instead of filtering the sections within the query, I moved it outside to the regular place filtering happens. This works as expected with the sections being filtered correctly by the filter text. @Query private var sections: [SectionsSD] let filter: String var filteredSections: [SectionsSD] { sections.filter { $0.toArticles.flatMap { $0.contains { $0.search.contains(filter) } } == true // or as before // $0.toArticles?.contains { $0.search.contains(filter) } == true } } List(filteredSections) { ... } So it's when placed inside the #Predicate are things not working. It is definitely worth noting that we are only in the second beta of SwiftData so there is always the chance things don't work because of internal bugs. For example, in the release notes is this which could be the reason your predicate isn't working. SwiftData Known Issues • SwiftData queries don't support some #Predicate flatmap and nil coalescing behaviors. (109723704) It's best to wait for the next few betas to see if anything changes, and maybe new features might make things easier.
Topic: UI Frameworks SubTopic: SwiftUI Tags:
Jun ’23
Reply to Print amount from single member instance on dynamic view
First of all, you need a way for the SingleMember view to communicate to the MainView and vice versa – a two-way communication. You can achieve this with the @Binding property wrapper. It can be used like this: struct SingleMember: View { @Binding var text: String var body: some View { TextField("Amount", text: $text) } } You then need to create a source-of-truth for those three values in the parent MainView that can also be passed to the child SingleMember views. You can either create three separate variables or an array of values depending on whether 3 is a fixed value or not. The binding can be created and passed around like this: struct MainView: View { @State private var values: [String] = .init(repeating: "", count: 3) var body: some View { VStack { ForEach(0..<3) { i in SingleMember(text: $values[i]) } Button("Print all values") { print(values) } } } }
Topic: UI Frameworks SubTopic: SwiftUI Tags:
Jun ’23
Reply to Customizing Selection Highlight Color for NavigationLink in NavigationSplitView
Just a note: the accentColor(_:) modifier is deprecated and the AccentColor in the Assets catalogue or the tint(_:) modifier should be used instead. First of all, make sure you have your System Settings > Appearance > Accent Colour set to "Multicolour" to allow different colours for apps' accent colours. By default, sidebar list items take on the app's accent colour (defined in the Assets catalogue). This is where your app's accent colour should go anyway. If it is defined in code, like you are doing, you could use the tint(_:) modifier instead, but I don't know if this method works.
Topic: UI Frameworks SubTopic: SwiftUI Tags:
Jun ’23
Reply to How to form a search predicate using swift data with a one to many model
"Cannot convert value of type 'Bool?' to closure result type 'Bool'" This is a simple Swift error. In your filter expression you are using optional chaining which is why it yields a result of type Bool?. The predicate closure, however, expects to be given a value of type Bool. Here is a fix for that particular error: let searchPredicate = #Predicate<SectionsSD> { $0.toArticles?.contains(filter) == true } ‎ "Instance method 'contains' requires the types 'ArticlesSD' and 'String.Element' (aka 'Character') be equivalent" $0.articles is of type [ArticlesSD]? and using contains on this array means you need to pass it a value of type ArticlesSD to check for. You are instead passing it a String (I'm assuming here that filter is a String) which is not what it expects. To fix this you can compare the filter to the search property in the contains closure. let searchPredicate = #Predicate<SectionsSD> { $0.toArticles?.contains { $0.search.contains(filter) } == true } And now you may think this works…but it doesn't. Swift spits out this error: "Optional chaining is not supported here in this predicate. Use the flatMap(_:) function explicitly instead. (from macro 'Predicate')". To fix this you can do what it says and wrap the original condition inside of a flatMap, like this: let searchPredicate = #Predicate<SectionsSD> { $0.toArticles.flatMap { $0.contains { $0.search.contains(filter) } } == true } Hope this helps (and works)!
Topic: UI Frameworks SubTopic: SwiftUI Tags:
Jun ’23