Type '()' cannot conform to 'MapAnnotationProtocol' - swiftui

I wrote a demo program to display pins on a map in SwiftUI following this and this tutorial and it worked no problem. Then when I applied this same technique to my app, I get an error that the type cannot conform to 'MapAnnotationProtocol'.
Here's my code:
import SwiftUI
import MapKit
struct MapLocationView: View {
#ObservedObject var manager = LocationManager()
#FetchRequest(sortDescriptors: [])
private var meals: FetchedResults<Meal>
var body: some View {
Map(coordinateRegion: $manager.region, annotationItems: meals) { meal in // error is on Map
if (meal.latitude != 0 && meal.longitude != 0) {
let coordinate = CLLocationCoordinate2D(latitude: meal.latitude, longitude: meal.longitude)
MapPin(coordinate: coordinate)
}
}
}
}
The LocationManager code is the same, unaltered code from the tutorial and from the demo I wrote. I don't understand why it works in the demo and doesn't work in my app.
I've tried wrapping the code in a Group {} but that didn't fix it. I've read other articles with similar error messages but none of those solutions seemed to apply in this case.
I'm pretty new to Swift and SwiftUI so it makes no sense to me that it should work in the demo but not in my app. I hope I've provided enough information for the problem to make sense.

A couple of things come to mind while looking at your code:
Does Meal conform to Identifiable?
Your closure should always return some sort of map annotation.
Your MapPin(.. ) should work, but it looks like you'll need to filter your array of Meal structs before that closure is called.
The following is untested code, but might help anyway.
extension Meal: Identifiable {
var id: ObjectIdentifier {
return ObjectIdentifier(self)
}
}
extension Meal {
var isValid: Bool {
return latitude != 0 && longitude != 0
}
var coordinate: CLLocationCoordinate2D {
return CLLocationCoordinate2D(latitude: latitude, longitude: longitude)
}
var body: some View {
Map(coordinateRegion:
$manager.region,
annotationItems: meals.filter { $0.isValid ) { meal in
MapPin(coordinate: meal.coordinate)
}
}

Related

How to bind to data that's part of an optional in SwiftUI

I'm curious, how do we specify a binding to State data that is part of an optional? For instance:
struct NameRecord {
var name = ""
var isFunny = false
}
class AppData: ObservableObject {
#Published var nameRecord: NameRecord?
}
struct NameView: View {
#StateObject var appData = AppData()
var body: some View {
Form {
if appData.nameRecord != nil {
// At this point, I *know* that nameRecord is not nil, so
// I should be able to bind to it.
TextField("Name", text: $appData.nameRecord.name)
Toggle("Is Funny", isOn: $appData.nameRecord.isFunny)
} else {
// So far as I can tell, this should never happen, but
// if it does, I will catch it in development, when
// I see the error message in the constant binding.
TextField("Name", text: .constant("ERROR: Data is incomplete!"))
Toggle("Is Funny", isOn: .constant(false))
}
}
.onAppear {
appData.nameRecord = NameRecord(name: "John")
}
}
}
I can certainly see that I'm missing something. Xcode gives errors like Value of optional type 'NameRecord?' must be unwrapped to refer to member 'name' of wrapped base type 'NameRecord') and offers some FixIts that don't help.
Based on the answer from the user "workingdog support Ukraine" I now know how to make a binding to the part I need, but the solution doesn't scale well for a record that has many fields of different type.
Given that the optional part is in the middle of appData.nameRecord.name, it seems that there might be a solution that does something like what the following function in the SwiftUI header might be doing:
public subscript<Subject>(dynamicMember keyPath: WritableKeyPath<Value, Subject>) -> Binding<Subject> { get }
My SwiftFu is insufficient, so I don't know how this works, but I suspect it's what is doing the work for something like $appData.nameRecord.name when nameRecord is not an optional. I would like to have something where this function would result in a binding to .constant when anything in the keyPath is nil (or even if it did a fatalError that I would avoid with conditionals as above). It would be great if there was a way to get a solution that was as elegant as Jonathan's answer that was also suggested by workingdog for a similar situation. Any pointers in that area would be much appreciated!
Binding has a failable initializer that transforms a Binding<Value?>.
if let nameRecord = Binding($appData.nameRecord) {
TextField("Name", text: nameRecord.name)
Toggle("Is Funny", isOn: nameRecord.isFunny)
} else {
Text("Data is incomplete")
TextField("Name", text: .constant(""))
Toggle("Is Funny", isOn: .constant(false))
}
Or, with less repetition:
if appData.nameRecord == nil {
Text("Data is incomplete")
}
let bindings = Binding($appData.nameRecord).map { nameRecord in
( name: nameRecord.name,
isFunny: nameRecord.isFunny
)
} ?? (
name: .constant(""),
isFunny: .constant(false)
)
TextField("Name", text: bindings.name)
Toggle("Is Funny", isOn: bindings.isFunny)

in SwiftUI, I have 2 Entities (A & B) in my CoreData with a relationship (one to many) between them, how can I fetch all attributes of B in TextFields

Let's say I have 2 entities:
GameSession :which has Attributes "date", "place", "numberofplayer" + a relationship called "players" with "Player"
Player: which has Attributes "name","score_part1","score_part2","score_part3" + a relationship with "GameSession"
the relationship is "one to many": One session can have many players
Let's say now I have a list of GameSession and when I click on on one (with a NavigationLink)
It sends me to a new view where I can see:
All the names of the players of that session (in text) and also right next to the player name I would like to have 3 TextField in which I can enter (an update) "score_part1","score_part2","score_part3" for every players of that session
Basically I am able to display the name of all the players of a given session, But it seems impossible to have the "score_part1","score_part2","score_part3" in editable TextField...
I have an error saying "Cannot convert value of type 'String' to expected argument type 'Binding<String>'"
Basically in my first view I have something like that:
struct RamiListePartieUIView: View {#Environment(.managedObjectContext) var moc#FetchRequest(entity: GameSession.entity(), sortDescriptors: [NSSortDescriptor(keyPath: \GameSession.date, ascending: false)]) var gamesessions: FetchedResults<GameSession>
var body: some View {
VStack {
List {
ForEach(gamesessions, id: \.date) { session in
NavigationLink (destination: DetailPartieSelecUIView(session: session)){
Text("\(session.wrappedPlace) - le \(session.wrappedDate, formatter: itemFormatter) ")
}
}
.onDelete(perform: deleteSessions)
.padding()
}
}
}
}
And in my second view I have something like that:
struct DetailPartieSelecUIView: View {
#State var session:GameSession
#Environment(\.managedObjectContext) var moc
var body: some View {
Section("Ma session du \(session.wrappedDate, formatter: itemFormatter)"){
ForEach(session.playersArray, id: \.self) { player in
HStack {
Text(player.wrappedName) // OK it works
TextField("score", text : player.wrappedScore_part1) // it generates an error
TextField("score", text : player.wrappedScore_part2) // it generates an error
TextField("score", text : player.wrappedScore_part3) // it generates an error
}
}
}
}
}
private let itemFormatter: DateFormatter = {
let formatter = DateFormatter()
// formatter.dateStyle = .short
// formatter.timeStyle = .medium
formatter.dateFormat = "YYYY/MM/dd" //"YY/MM/dd"
return formatter
}()
also,
I have defined the "wrappedScore_part1","wrappedScore_part2","wrappedScore_part3" in the Player+CoreDataProperties.swift file
and "wrappedPlace", "wrappedData" as well as the "PlayersArray" in the GameSession+CoreDataProperties.swift file
it is done like that:
public var wrappedPlace: String {
place ?? "Unknown"
}
// Convert NSSet into an array of "Player" object
public var playersArray: [Player] {
let playersSet = players as? Set<Player> ?? []
return playersSet.sorted {
$0.wrappedName< $1.wrappedName
}
}
I am new at coding with swiftUI so I am probably doing something wrong... If anyone can help me it would be much appreciated.
Thanks a lot
I have tried a lot of things. Like changing the type of my attribute to Int32 instead os String. As I am suppose to enter numbers in those fields, I thought it would be best to have Integer. But it didn't change anything. and ultimately I had the same kind of error message
I tried also to add the $ symbol, like that:
TextField("score", text : player.$wrappedScore_part1)
But then I had other error message popping up at the row of my "ForEach", saying "Cannot convert value of type '[Player]' to expected argument type 'Binding'"
And also on the line just after the HStack, I had an error saying "Initializer 'init(_:)' requires that 'Binding' conform to 'StringProtocol'"
Thank you for your help!
Best regards,
JB
Your first problem of how to fetch the players in a session you need to supply a predicate to the #FetchRequest<Player>, e.g.
#FetchRequest
private var players: FetchedResults<Player>
init(session: Session) {
let predicate = NSPredicate(format: "session = %#", session)
let sortDescriptors = [SortDescriptor(\Player.timestamp)] // need something to sort by.
_players = FetchRequest(sortDescriptors: sortDescriptors, predicate: predicate)
}
That acts like a filter and will only return the players that have the session relation equalling that object. The reason you have to fetch like this is so any changes will be detected.
The second problem about the bindings can be solved like this:
struct PlayerView: View{
#ObservedObject var player: Player {
var body:some View {
if let score = Binding($player.score) {
TextField("Score", score)
}else{
Text("Player score missing")
}
}
}
This View takes the player object as an ObservedObject so body will be called when any of its properties change and allows you to get a binding to property. The Binding init takes an optional binding and returns a non-optional, allowing you to use it with a TextField.

SwiftUI GKLeaderboard loadEntries

I would like to add leaderboards to my SwiftUI app.
I can't find any examples of using loadEntries to load leaderboard values.
I tried the following...
let leaderBoard: GKLeaderboard = GKLeaderboard()
leaderBoard.identifier = "YOUR_LEADERBOARD_ID_HERE"
leaderBoard.timeScope = .allTime
leaderBoard.loadScores { (scores, error) in ...
This results in the following warnings:
'identifier' was deprecated in iOS 14.0: Use
loadEntriesForPlayerScope:timeScope:range:completionHandler: instead.
'timeScope' was deprecated in iOS 14.0: Use
loadEntriesForPlayerScope:timeScope:range:completionHandler: instead.
'loadScores(completionHandler:)' was deprecated in iOS 14.0: Use
loadEntriesForPlayerScope:timeScope:range:completionHandler:.
using loadEntriesForPlayerScope results in the following warning:
'loadEntriesForPlayerScope(_:timeScope:range:completionHandler:)' has
been renamed to 'loadEntries(for:timeScope:range:completionHandler:)'
Using loadEntries I don't know how to specify the leaderboard identifier.
Here is simple demo of possible approach - put everything in view model and load scores on view appear.
import GameKit
class BoardModel: ObservableObject {
private var board: GKLeaderboard?
#Published var localPlayerScore: GKLeaderboard.Entry?
#Published var topScores: [GKLeaderboard.Entry]?
func load() {
if nil == board {
GKLeaderboard.loadLeaderboards(IDs: ["YOUR_LEADERBOARD_ID_HERE"]) { [weak self] (boards, error) in
self?.board = boards?.first
self?.updateScores()
}
} else {
self.updateScores()
}
}
func updateScores() {
board?.loadEntries(for: .global, timeScope: .allTime, range: NSRange(location: 1, length: 10),
completionHandler: { [weak self] (local, entries, count, error) in
DispatchQueue.main.async {
self?.localPlayerScore = local
self?.topScores = entries
}
})
}
}
struct DemoGameboardview: View {
#StateObject var vm = BoardModel()
var body: some View {
List {
ForEach(vm.topScores ?? [], id: \.self) { item in
HStack {
Text(item.player.displayName)
Spacer()
Text(item.formattedScore)
}
}
}
.onAppear {
vm.load()
}
}
}
I might be stating the obvious but have you looked at the WWDC20 videos?
Usually when there are big changes like this they cover it during WWDC that year.
Tap into Game Center: Leaderboards, Achievements, and Multiplayer
Tap into Game Center: Dashboard, Access Point, and Profile
I haven't looked at the videos but the documentation eludes that identifier might be replaced by var baseLeaderboardID: String

SwiftUI - ReferenceFileDocument - Inability to indicate a document needs saving

When creating a class conforming to ReferenceFileDocument, how do you indicate the document needs saving. i.e. the equivalent of the NSDocument's updateChangeCount method?
I've met the same problem that the SwiftUI ReferenceFileDocument cannot trigger the update. Recently, I've received feedback via the bug report and been suggested to register an undo.
Turns out the update of ReferenceFileDocument can be triggered, just like UIDocument, by registering an undo action. The difference is that the DocumentGroup explicitly implicitly setup the UndoManager via the environment.
For example,
#main
struct RefDocApp: App {
var body: some Scene {
DocumentGroup(newDocument: {
RefDocDocument()
}) { file in
ContentView(document: file.document)
}
}
}
struct ContentView: View {
#Environment(\.undoManager) var undoManager
#ObservedObject var document: RefDocDocument
var body: some View {
TextEditor(text: Binding(get: {
document.text
}, set: {
document.text = $0
undoManager?.registerUndo(withTarget: document, handler: {
print($0, "undo")
})
}))
}
}
I assume at this stage, the FileDocument is actually, on iOS side, a wrapper on top of the UIDocument, the DocumentGroup scene explicitly implicitly assign the undoManager to the environment. Therefore, the update mechanism is the same.
The ReferenceFileDocument is ObservableObject, so you can add any trackable or published property for that purpose. Here is a demo of possible approach.
import UniformTypeIdentifiers
class MyTextDocument: ReferenceFileDocument {
static var readableContentTypes: [UTType] { [UTType.plainText] }
func snapshot(contentType: UTType) throws -> String {
defer {
self.modified = false
}
return self.storage
}
#Published var modified = false
#Published var storage: String = "" {
didSet {
self.modified = true
}
}
}
ReferenceFileDocument exists for fine grained controll over the document. In comparison, a FileDocument has to obey value semantics which makes it very easy for SwiftUI to implement the undo / redo functionality as it only needs to make a copy before each mutation of the document.
As per the documentation of the related DocumentGroup initializers, the undo functionality is not provided automatically. The DocumentGroup will inject an instance of an UndoManger into the environment which we can make use of.
However an undo manager is not the only way to update the state of the document. Per this documentation AppKit and UIKit both have the updateChangeCount method on their native implementation of the UI/NSDocument object. We can reach this method by grabbing the shared document controller on macOS from within the view and finding our document. Unfortunately I don't have a simple solution for the iOS side. There is a private SwiftUI.DocumentHostingController type which holds a reference to our document, but that would require mirroring into the private type to obtain the reference to the native document, which isn't safe.
Here is a full example:
import SwiftUI
import UniformTypeIdentifiers
// DOCUMENT EXAMPLE
extension UTType {
static var exampleText: UTType {
UTType(importedAs: "com.example.plain-text")
}
}
final class MyDocument: ReferenceFileDocument {
// We add `Published` for automatic SwiftUI updates as
// `ReferenceFileDocument` refines `ObservableObject`.
#Published
var number: Int
static var readableContentTypes: [UTType] { [.exampleText] }
init(number: Int = 42) {
self.number = number
}
init(configuration: ReadConfiguration) throws {
guard
let data = configuration.file.regularFileContents,
let string = String(data: data, encoding: .utf8),
let number = Int(string)
else {
throw CocoaError(.fileReadCorruptFile)
}
self.number = number
}
func snapshot(contentType: UTType) throws -> String {
"\(number)"
}
func fileWrapper(
snapshot: String,
configuration: WriteConfiguration
) throws -> FileWrapper {
// For the sake of the example this force unwrapping is considered as safe.
let data = snapshot.data(using: .utf8)!
return FileWrapper(regularFileWithContents: data)
}
}
// APP EXAMPLE FOR MACOS
#main
struct MyApp: App {
var body: some Scene {
DocumentGroup.init(
newDocument: {
MyDocument()
},
editor: { file in
ContentView(document: file.document)
.frame(width: 400, height: 400)
}
)
}
}
struct ContentView: View {
#Environment(\.undoManager)
var _undoManager: UndoManager?
#ObservedObject
var document: MyDocument
var body: some View {
VStack {
Text(String("\(document.number)"))
Button("randomize") {
if let undoManager = _undoManager {
let currentNumber = document.number
undoManager.registerUndo(withTarget: document) { document in
document.number = currentNumber
}
}
document.number = Int.random(in: 0 ... 100)
}
Button("randomize without undo") {
document.number = Int.random(in: 0 ... 100)
// Let the system know that we edited the document, which will
// eventually trigger the auto saving process.
//
// There is no simple way to mimic this on `iOS` or `iPadOS`.
let controller = NSDocumentController.shared
if let document = controller.currentDocument {
// On `iOS / iPadOS` change the argument to `.done`.
document.updateChangeCount(.changeDone)
}
}
}
}
}
Unfortunatelly SwiftUI (v2 at this moment) does not provide a native way to mimic the same functionality, but this workaround is still doable and fairly consice.
Here is a gist where I extended the example with a custom DocumentReader view and a DocumentProxy which can be extended for common document related operations for more convenience: https://gist.github.com/DevAndArtist/eb7e8aa5e7134610c20b1a7aca358604

SwiftUI onDelete List with Toggle

This is my third question on this issue. So far there was no solution that didn't crash. I want to swipe-delete on a List with Toggles. My (simplified) code looks like this:
struct Item: Identifiable {
var id = UUID()
var isOn: Bool
}
struct ContentView: View {
#State var items = [Item(isOn: true) , Item(isOn: false), Item(isOn: false)]
var body: some View {
NavigationView {
List {
ForEach(items) {item in
Toggle(isOn: self.selectedItem(id: item.id).isOn)
{Text("Item")}
}.onDelete(perform: delete)
}
}
}
func delete(at offsets: IndexSet) {
self.items.remove(atOffsets: offsets)
}
func selectedItem(id: UUID) -> Binding<Item> {
guard let index = self.items.firstIndex(where: {$0.id == id}) else {
fatalError("Item does not exist")
}
return self.$items[index]
}
}
I tried different solutions, e.g. with .indices and .enumerated() and looping over the indices. The solution with the func selectedItem() is from https://troz.net/post/2019/swiftui-data-flow/, which is a nice idea to get a Bindable from item.
If I try to swipe-delete the list items, I always get this error:
Thread 1: Fatal error: Index out of range
I'd really like to understand why this happens, but XCodes error messages doesn't really help. I posted similar questions here: SwiftUI ForEach with .indices() does not update after onDelete (see comment) and here: SwiftUI: Index out of range when deleting cells with toggle.
I really hope someone can help on this issue, because I try to find a solution on the internet for a few days but none of the suggested solutions really worked out for me.
Thanks, Nico
Here is fixed part of code (tested with Xcode 11.4 / iOS 13.4)
func selectedItem(id: UUID) -> Binding<Item> {
guard let index = self.items.firstIndex(where: {$0.id == id}) else {
fatalError("Item does not exist")
}
// Don't use direct biding to array element as it is preserved and
// result in crash, use computable standalone binding instead !!
return Binding(get: {self.items[index]}, set: {self.items[index] = $0})
}