How to use the Transferable protocol in SwiftUI tables - swift

I am building a macOS app in Swift, using SwiftUI. I have the following struct:
struct focusArticle: Identifiable {
let category: String
let headline: String
let body: String
let publishedDate: String
let source: String
var id = UUID()
}
I have created an array with instances of focusArticle that are being used to populate a Table element, like so
struct ArticleView: View {
#State private var selectedArticles = Set<focusArticle.ID>()
var body: some View {
Table(articles, selection: $selectedArticles) {
TableColumn("Category", value: \.category)
.width(70)
TableColumn("Headline", value: \.headline)
.width(350)
TableColumn("Published", value: \.publishedDate)
}
.frame(width: 600, height: 200)
}
}
I want to make each row in this table draggable, so I can drag-drop from this table, to a similar table in another view. From reading Apple's documentation, it seems like I need to do two things.
Make my focusArticle struct conform to the Transferable protocol.
Use the .draggable modifier on the table to make the elements draggable.
I can't seem to do either of these things. Copying over the example code from the documentation into my struct makes it throw an error — Static method 'buildBlock' requires that 'String' conform to 'TransferRepresentation'. Adding a .draggable modifier to the table throws an expected error: Instance method 'draggable' requires that '[focusArticle]' conform to 'Transferable'
EDIT: This is one of the ways I have tried to make focusArticles confirm to Transferable.
struct focusArticle: Identifiable, Codable, Transferable {
let category: String
let headline: String
let body: String
let publishedDate: String
let source: String
var id = UUID()
static var transferRepresentation: some TransferRepresentation {
CodableRepresentation(contentType: .focusArticle)
}
}
This shows an error Type 'UTType' has no member 'focusArticle'

You need to add a UTType, as an extension like this:
extension UTType {
static var focusArticle = UTType(exportedAs: "com.yourdomain.yourapp.focusarticle")
}

Related

Is it possible to make a customisable & reusable SwiftUI Form?

I want the to be able to create a Form in SwiftUI by creating a custom model and defining what fields are needed. Eventually, I want the User to be able to do this to make their own custom forms and data models, but for now I just want to reduce the code so that I can just quickly "Write" different types of Form and use a For loop to generate the form.
I.e. I have basically duplicate pages of code for different types of forms, which all work the same but just have different values and it seems like a waste, and I could just have some kind of single view and feed the data needed to that so I can reuse it, and eventually allow users to create their own custom forms in the future.
Here is what I have come up with so far as an example (Just playing around and trying to see what might be possible):
import SwiftUI
import Foundation
var fieldTypes = ["Field", "Slider", "List", "Date"...]
struct Worksheet: Codable, Identifiable, Hashable {
//data for populating Form
var id: String
var name: String?
var fields: [Field]
//data for saving model and values of form
var title: String?
var date: Date
var severity: Double
var trigger: String?
var description: String
var symptoms: [String]
var nInterpretation: String?
var rResponse: String
}
struct Field: Codable, Identifiable, Hashable {
var id: String
var type: String
var title: String
var description: String
var footer: String
}
class Dev_VM: ObservableObject {
var fields = [Field(id: "i", type: "Slider", title: "SliderTitle", description: "Description", footer: "Footer"), Field(id: "id2", type: "TextField", title: "TextFieldTitle", description: "Description", footer: "Footer")]
}
//Form view for data entry
struct Dev_FormView: View {
#Environment(\.presentationMode) var presentation
#ObservedObject var vm = Dev_VM()
static let taskDateFormat: DateFormatter = {
let dateFormatter = DateFormatter()
dateFormatter.dateStyle = .full
dateFormatter.timeStyle = .short
dateFormatter.locale = Locale.current
return dateFormatter
}()
var body: some View {
Form {
//Section for selecting date.
for field in vm.fields {
switch field.type {
case "TextField":
Section(header: Text(field.title), footer: Text(field.footer)) {
ZStack {
TextEditor(text: $title)
.disabled(!editMode)
Text(title).opacity(0).padding(.all, 8)
}
}
case "Slider":
HStack {
Slider(value: $value, in: 0...100, step: 1)
.disabled(true)
Text("\(Int(severity))")
}
default:
print("Empty")
}
}
}
.navigationBarTitle("Dev_Playground", displayMode: .inline)
}
}
Seems that there are problems, for example on the for statement I get the error Closure containing control flow statement cannot be used with result builder 'ViewBuilder'
Another problem seems to be populating the values, as I will need #State variables, because the fields are dynamic, could I make these dynamic too?
Does anyone think that what I'm trying to do might be possible? Thank you!

Pass struct as generic type and access that generic types properties

I'm working on a Swift package where an option will be to pass in a generic type (Person) and then that GenericStruct can use properties on that type passed in. The issue obviously is that the generic T has no idea what's being passed in. Is there a way to define the property to access on the generic type T?
struct Person: Equatable {
var name: String
var height: Double
}
struct ContentView: View {
#State private var james = Person(name: "James", height: 175.0)
var body: some View {
GenericStruct(person: $james)
}
}
struct GenericStruct<T: Equatable>: View {
#Binding var person: T
var body: some View {
Text(person.name)) // This line.
}
}
I want to specifically pass in which property to access on Person when passing it to GenericStruct. The property won't always be name it could be anything I define within Person. For example:
GenericStruct(person: $james, use: Person.name)
Isn't this exactly a protocol?
protocol NameProviding {
var name: String { get }
}
struct GenericStruct<T: Equatable & NameProviding>: View { ... }
Or is there a more subtle part of the question?
The best way to do this is to pass a String Binding:
struct GenericStruct: View {
#Binding var text: String
var body: some View {
Text(text)) // This line.
}
}
GenericStruct(text: $james.name)
But it is possible with key paths. It's just a bit more awkward and less flexible in this particular case:
// Should be the syntax, but I haven't double-checked it.
struct GenericStruct<T: Equatable>: View {
#Binding var person: T
var use: KeyPath<T, String>
var body: some View {
Text(person[keyPath: use]))
}
}
GenericStruct(person: $james, use: \.name)

SwiftUI TextField Binding to a simple Model (non-class sturct)

I have a simple struct, that is decodable / codable and hashable.
public struct Field: Codable, Hashable {
let key: String
enum CodingKeys: String, CodingKey {
case key
}
}
In my SwiftUI, I'm creating an array, where I want to add and remove and Bind to the Struct values. But I am getting errors, that Struct is not Binding.
let decodableJSON = """
{
"key": ""
}
"""
let settableInit: Field = try! JSONDecoder().decode(Field.self, from: decodableJSON.data(using: .utf8)!)
struct Test_view: View {
#State
var settableFields: [Field] = [settableInit]
var body: some View {
ForEach(settableFields, id: \.key) { (settableField: Field) in
TextField("Key", text: settableField.key)
}
But I get an error, that settableField is not Binding. I have tried adding the settableInit as an #ObservableObject to the main Swift View, but it still doesn't work.
Is there a way, to have the View bind to Struct properties, and have TextField change these properties? It feels like something so trivial, but for some reason undoable for me.
Thank you for any pointers!
In Xcode13 you can use the new element binding syntax:
public struct Field: Codable, Hashable {
var key: String
enum CodingKeys: String, CodingKey {
case key
}
}
struct Demo: View {
#State var settableFields: [Field] = [Field(key: "1"), Field(key: "2")]
var body: some View {
ForEach($settableFields, id: \.key) { $settableField in
TextField("Key", text: $settableField.key)
}
}
}
In earlier versions of Xcode you could use the indices of the array:
struct Demo: View {
#State var settableFields: [Field] = [Field(key: "1"), Field(key: "2")]
var body: some View {
ForEach(settableFields.indices, id: \.self) { index in
TextField("Key", text: $settableFields[index].key)
}
}
}

Struct's id of type UUID changes everytime i try to access it [duplicate]

This question already has answers here:
SwiftUI: data identifier stability and uniqueness
(2 answers)
Closed 1 year ago.
I have a struct which is parsed from JSON, but contains another struct Article that must be identifiable. It looks like this:
import Foundation
struct TopHeadlines: Codable {
var totalArticles: Int
var articles: [Article]
struct Article: Codable {
var title: String
var description: String
var url: String
var image: String
var publishedAt: String
var source: Source
}
struct Source: Codable {
var name: String
var url: String
}
var json: Data? {
return try? JSONEncoder().encode(self)
}
}
extension TopHeadlines.Article: Identifiable {
var id: UUID { return UUID() }
}
I need UUID generated to access image from newImages dictionary:
List(viewModel.articles, id: \.id) { article in
HStack {
OptionalImage(uiImage: viewModel.newsImages[article.id])
Text(article.id.uuidString)
Text(article.id.uuidString)
Text(article.id.uuidString)
}
}
but three text views print three different UUIDs:
CC83B8AE-61B1-4A7D-A8A4-1B1E98C27CE7
545C1D28-F098-48A3-8C3C-A98BB54F9751
39B8383C-A2D8-46B0-BA51-1B861AF09762
How should I create ID for Article struct so it wouldnt be re-generated everytime?
You've declared id as a computed property, so by definition a new UUID instance will be returned every time you access the property.
You need to make the property a stored immutable property to ensure that the UUID never changes.
You also need to manually declare a CodingKey conformant enum and omit the id key from its cases to tell the compiler that id should not be decoded from the JSON.
struct TopHeadlines: Codable {
var totalArticles: Int
var articles: [Article]
struct Article: Codable, Identifiable {
var title: String
var description: String
var url: String
var image: String
var publishedAt: String
var source: Source
let id = UUID()
private enum CodingKeys: String, CodingKey {
case title, description, url, image, publishedAt, source
}
}
struct Source: Codable {
var name: String
var url: String
}
var json: Data? {
return try? JSONEncoder().encode(self)
}
}
The id property in your extension is a computed property, so a new UUID (UUID()) is generated on each call.
Since you can't have stored properties in an extension, try adding it directly to the Article struct, like this:
struct Article: Codable, Identifiable {
var id = UUID()
var title: String
var description: String
var url: String
var image: String
var publishedAt: String
var source: Source
}
This only generates a UUID when the struct is initialized.
extensions cannot store properties, that's why you've implemented id as computed property, and each new UUID() is unique
the most obvious solution is to move it to move it to your struct
struct Article: Codable {
let uuid = UUID()
// ...
}
Declared like this it won't require value both in time of decoding or creating new object.
But if you can't edit this struct(which is probably why you're using an extension), you can do the following: extend your struct with Hashable, then you can access object hashValue which is calculated based on all properties so this value will be the same only for two objects with same values in all properties, which is usually fits good for an unique identifier
extension Article: Hashable {
}
Usage
Text("\(article.hashValue)")

Why can't a constant property of a struct be used in a following example

I have a following code from the Swift Design Patterns book:
protocol Identifiable {
associatedtype ID
static var idKey: WritableKeyPath<Self, ID> { get }
}
struct Book: Identifiable {
static let idKey = \Book.isbn
var isbn: String
var title: String
}
It works fine. However, if I change the Book declaration using let instead of var for the isbn property, I receive an error: Type 'Book' does not conform to protocol 'Identifiable'. So the whole erroneous code looks like:
protocol Identifiable {
associatedtype ID
static var idKey: WritableKeyPath<Self, ID> { get }
}
struct Book: Identifiable { // error: Type 'Book' does not conform to protocol 'Identifiable'
static let idKey = \Book.isbn
let isbn: String
var title: String
}
I'm curious why does this happen. I try to run the code inside Xcode Playground file.
It's a WritableKeyPath - you need to write to it.
It must be a variable in order to be writable.
In your Book struct you are instantiating a KeyPath with a literal.
This fails when the KeyPath is not a WritableKeyPath
From the docs:
"A key path that supports reading from and writing to the resulting value."
Meaning that the underlying value must be a variable.
The Following does compile:
import UIKit
//https://iswift.org/playground?ZEJ6cL&v=4
protocol Identifiable {
associatedtype ID
static var idKey: WritableKeyPath<Self, ID> { get }
}
struct Book: Identifiable {
static let idKey = \Book.title
let isbn: String
var title: String
}