I tried the entire day to find the simplest solution to sort a #FetchRequest without any success!
I have tried this little modification:
#AppStorage("sortTitle") private var sortTitle = true
#Environment(\.managedObjectContext) private var viewContext
#FetchRequest(sortDescriptors: [ sortTitle ? SortDescriptor(\.title) : SortDescriptor(\.date) ]) private var items: FetchedResults<Item>
And of course it doesn't work. Actually, I'm looking for something very simple because this isn't a dynamic sorting ; it's more like a one-time sorting that can be done by toggling sortTitle from the Settings screen.
There's of course one online solution (on the link below) but I'm not good enough to understand it correctly at the moment!
https://www.youtube.com/watch?v=O4043RVjCGU
Thanks, in advance, for your feedback. :)
This is worth a shot but to be honest #FetchRequest seems to be designed for a a ContentView that is only init once which is not the way SwiftUI is supposed to work.
.onAppear {
items.sortDescriptors = sortTitle ? [SortDescriptor(\.title)] : [SortDescriptor(\.date)]
}
.onChange(of: sortTitle) { newSortTitle in
items.sortDescriptors = sortTitle ? [SortDescriptor(\.title)] : [SortDescriptor(\.date)]
}
The flaw is that if the View containing this code is re-init, the change made to the sortDescriptors is lost.
Related
This is on iOS 16. I'm on Xcode 14.0.
I have the following view:
struct ContentView: View {
struct Ocean: Identifiable, Hashable {
let name: String
let id = UUID()
}
private var oceans = [
Ocean(name: "Pacific"),
Ocean(name: "Atlantic"),
Ocean(name: "Indian"),
Ocean(name: "Southern"),
Ocean(name: "Arctic")
]
#State private var multiSelection = Set<UUID>()
var body: some View {
NavigationView {
List(oceans, selection: $multiSelection) {
Text($0.name)
}
.navigationTitle("Oceans")
.toolbar { EditButton() }
}
Text("\(multiSelection.count) selections")
}
}
This code is taken from https://developer.apple.com/documentation/SwiftUI/List.
I am expecting to see that whenever I click on the "Edit" button, I should be able to select a few items, press "Done", then the bottom would still show the number of items I have selected. However, this is not the case:
I tried to use a debugger, and I found out that whenever I click on "Done" after selecting the items, the multiSelection resets itself to be empty. This used to work on Xcode 13. I can't really find anything on Apple's documentation regarding changes to the EditButton or changes to the List struct.
Update
I filed a bug report and Apple got back to me, they said this is expected behaviour. I guess I misinterpreted the use case for this list selection here.
Before iOS 16 the selection only worked when in editing mode. Now it works also when not editing so I believe the problem is now the selection is being cleared when done is being tapped (so it can be used when not editing).
I think we need to send feedback to request 2 selection bindings, one for editing and one for when not-editing.
i think the issue here is that your identifiers for your list are not stable. Basically every time an ocean object is created, it gets a new UUID. You dont want that UUID to change.
Any time your state property changes, the view may or may not get rebuilt, causing your oceans to get regenerated.
Try storing your oceans in like this:
#State var oceans: [Ocean] = [
Ocean(name: "Pacific"),
Ocean(name: "Atlantic"),
Ocean(name: "Indian"),
Ocean(name: "Southern"),
Ocean(name: "Arctic")
]
Alternatively, you can use the ocean's name as its identifier instead of a UUID that is generated each time its created.
From what I've read whenever you instantiate an object yourself in your view, you should use #StateObject instead of #ObservedObject. Because apparently if you use #ObservedObject, SwiftUI might decide in any moment to throw it away and recreate it again later, which could be expensive for some objects. But if you use #StateObject instead then apparently SwiftUI knows not to throw it away.
Am I understanding that correctly?
My question is, how does #StateObject communicate that to SwiftUI? The reason I'm asking is because I've made my own propertyWrapper which connects a view's property to a firebase firestore collection and then starts listening for live snapshots. Here's an example of what that looks like:
struct Room: Model {
#DocumentID
var id: DocumentReference? = nil
var title: String
var description: String
static let collectionPath: String = "rooms"
}
struct MacOSView: View {
#Collection({ $0.order(by: "title") })
private var rooms: [Room]
var body: some View {
NavigationView {
List(rooms) { room in
NavigationLink(
destination: Lazy(ChatRoom(room))
) {
Text(room.title)
}
}
}
}
}
The closure inside #Collection is optional, but can be used to build a more precise query for the collection.
Now this works very nicely. The code is expressive and I get nice live-updating data. You can see that when the user would click on a room title, the navigation view would navigate to that chat room. The chatroom is a view which shows all the messages in that room. Here's a simplified view of that code:
struct ChatRoom: View {
#Collection(wait: true)
private var messages: [Message]
// I'm using (wait: true) to say "don't open a connection just yet,
// because I need to give you more information that I can't give you yet"
// And that's because I need to give #Collection a query
// based on the room id, which I can only do in the initializer.
init(_ room: Room) {
_messages = Collection { query in
query
.whereField("room", isEqualTo: room.id!)
.order(by: "created")
}
}
var body: some View {
List(messages) { message in
MessageBubble(message: message)
}
}
}
But what I've noticed, is that SwiftUI initializes a new messages-collection every single time the user interacts with the UI in any way. Like even when an input box is clicked to give it focus. This is a huge memory leak. I don't understand why this happens, but is there a way to tell SwiftUI to only initialize #Collection once, just like it does with #StateObject?
Use #StateObject when you want to define a new source-of-truth reference type owned by that View and tied to its life-cycle. The object will be created just before the body is run for the first time and is stored in a special place by SwiftUI. If the View struct is recreated (e.g. by a parent View's body recompute by a state change) then the previous object will be set on the property instead of a new object. If the View is no longer init during body updates then the object will be deinit.
When you pass a #StateObject into a child View then in that child View uses #ObservedObject to enable the body of the child view to be recomputed when the object changes, just the same way the body of the parent View will also be recomputed because it used an #StateObject. If you use #ObservedObject for an object that was not an #StateObject in a parent View then since no View is owning it then it will be lost every time the View is init during body updates. Also, any objects that you create in the View init, like your Collection, those are immediately lost too. These excessive heap allocations could cause a leak and slows down SwiftUI. Objects the View owns must be wrapped in #StateObject to avoid this.
Lastly, don't use #StateObject for view state and certainly don't try and implement a legacy "View Model" pattern with them. We have #State and #Binding for that. #StateObject is only for model objects (as in your domain types: Book, Person etc.), loaders or fetchers.
WWDC 2020 Data Essentials in SwiftUI explains all this very nicely.
I found out that "keyboardShortcut(_:)" was released in the new Xcode12, and I thought I could get events by typing them with the keyboard, but I don't know how to use it.
I'm sorry, but I'd like to hear more about how to use it with examples.
Thank you in advance for your help.
editing1.............
I found out that I can use keyboardShortcut to make the button action.
MYCODE
struct ContentView: View {
var body: some View {
Button("button"){
print("push_A")
}
.keyboardShortcut("a",modifiers: .option)
}
}
Do I have to place a myriad of buttons in order to use keybordShortcut?
I've been implementing Redux in my SwiftUI Project successfully but struggle when it comes to handling bindings properly. I want to use the binding functionality of SwiftUI while also storing the information in the Redux State.
As you see this kind of contradicts itself since the state can't be bound two-way.
This is my current code
#State var tab: Tab = .tab1
TabView(selection: $tab) { ... }
Ideally it should be comfortably usable like this if it's possible. I am also open to other ideas, that's just what i initially came up with - it's far from perfect.
#State var store = ReduxStore(...)
TabView(selection: $store.state.tab) { ... }
I figured out how to handle such cases. The solution for me is a Binding like:
let tabBinding = Binding<Tab> (
get: { self.store.state.currentTab }, // return the value from the state
set: { self.store.dispatch(action: NavigationAction.updateTab(tab: $0)) } // send the action here
)
Although this probably could be beautified it works, is pretty straight forward and solves the problem of working with Bindings in Redux.
I'm trying to make a Picker with SwiftUI. I've follow a tutorial but don't have the same result. There is Ambiguous reference on the self.category.count and self.category[$0]. After one entire day, I still don't know how to fix it ...
import SwiftUI
struct Picker : View {
var category = ["Aucun", "BF Glaive", "Baguette", "Negatron", "Larme", "Ceinture", "Arc", "Cotte", "Spatule"]
#State private var selectedCategory = 0
var body: some View {
VStack {
Picker(selection: $selectedCategory, label: Text("Item")) {
ForEach(0 ..< self.category.count) {
Text(self.category[$0])
.tag($0)
}
}
Text("Selected : \(category[selectedCategory])")
}
}
}
To resolve name conflicts between modules, you can either:
Rename your Picker to something else.
Use the qualified (full) name:
SwiftUI.Picker(selection: $selectedCategory, label: Text("Item")) {
The error message Ambiguous reference to member 'count’ is misleading. What you have is a naming conflict between SwiftUI.Picker and your Picker struct. Just change the name of your struct to something other than Picker. For example:
struct CategoryPicker : View {
// ...
}
Alternatively, you can resolve the naming conflict between the modules by providing the fully qualified name for SwiftUI.Picker (as Sulthan pointed out):
SwiftUI.Picker(selection: $selectedCategory, label: Text("Item")) {
// ...
}
However, I wouldn’t advise this option unless your intention is to replace SwiftUI.Picker everywhere in your app. Your code includes the category array and a Text view, so it's unlikely this is what you're after.
If the app eventually needs OtherPicker with a SwiftUI.Picker and the module name is omitted again, it’ll be even more confusing to track down the error—and you’ve already spent an “entire day” on it. So, best to avoid this possibility by not introducing the conflict at all :)