I was looking for some Code (SwiftUI) to get a JSON File from a website into my Xcode Project, and I found a good Sample but when I try to change the Code and wanna use a 'var' from one struct in an other struct.
struct Course: Decodable, Identifiable, Hashable {
let id: Int
let name: String
var link: String
let imageUrl: String
let number_of_lessons: Int
}
class NetworkManager: ObservableObject {
#Published var courses = [Course]()
func getAllCourses() {
guard let url = URL(string: "https://api.letsbuildthatapp.com/jsondecodable/courses") else { return }
URLSession.shared.dataTask(with: url) { data, response, error in
do {
let courses = try JSONDecoder().decode([Course].self, from: data!)
DispatchQueue.main.async {
self.courses = courses
print(courses)
}
} catch {
print("Failed To decode: ", error)
}
}.resume()
}
init() {
getAllCourses()
}
}
struct SwiftUIView: View {
var cs:Course
var body: some View {
Text(cs.name)
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
SwiftUIView() //here it says Missing argument for parameter...
}
Xcode says: Missing argument for parameter 'cs' in call, Insert 'cs:<#Course#>'.
As you are trying to initialize a struct, you need to pass all properties with setters, here that is cs, as body is a get-only property.
You will need to pass a Course object via SwiftUIView(cs: course). This course can be just a static course, as it's only used in your SwiftUI preview.
Swift autogenerates initializers for structs, so your SwiftUIView has one like this:
public init(cs: Course) {
self.cs = cs
}
So in order to create an instance of your SwiftUIView in the previews you have to declare it like this:
struct ContentView_Previews: PreviewProvider {
static let cs = Course(id: 7,
name: "The best course",
link: "thebestcourse.com",
imageUrl: "thebestcourse.com/image.jpg",
number_of_lessons: 5)
static var previews: some View {
SwiftUIView(cs: cs)
}
}
Related
I present this view as a sheet from its parent view
struct NamesView: View {
#Binding var match: Match
var body: some View {
...
}
}
Since the match source of truth is in the parent view presenting this NamesView sheet, when the view is constructed I pass in a $match binding and data flows as intended.
However, when constructing this view in a preview provider
struct NamesView_Previews: PreviewProvider {
static var previews: some View {
NamesView()
}
}
the compiler says that NamesView() expects a match argument of type Binding<Match> (Match being the parent view presenting this view as a sheet). I'm not sure what would be a good way to proceed from here or if this is a limitation of SwiftUI.
If you want only constant preview, then it can be
struct NamesView_Previews: PreviewProvider {
static var previews: some View {
NamesView(match: .constant(Match()))
}
}
if you want it in live, the it can be
struct NamesView_Previews: PreviewProvider {
struct BindingTestHolder: View {
#State private var testedMatch = Match()
var body: some View {
NamesView(match: $testedMatch)
}
}
static var previews: some View {
BindingTestHolder()
}
}
Try this:
struct NamesView_Previews: PreviewProvider {
static var previews: some View {
NamesView(match:.constant(Match()))
}
}
I wrote about this in depth here, but the short version is simply to add the following extension:
extension Binding {
public static func variable(_ value: Value) -> Binding<Value> {
var state = value
return Binding<Value> {
state
} set: {
state = $0
}
}
}
And then do...
struct NamesView_Previews : PreviewProvider {
static var previews: some View {
NamesView(match: .variable(Match()))
}
}
This lets you actually mutate the value, useful when doing live previews in Xcode 14.
I think I'm going about this SwiftUI thing all wrong. It's clear that we're just defining the layout as a structs and there can be limited conventional programming embroiled in the layout. I'm having difficulties thinking like this. What is the best way of doing this?
Take the example below. Project is an NSManagedObject. All I want to do is pass in example record so the SwiftUI will render. Nothing I try works.
struct ProjectView: View
{
#State var project: Project //NSManagedObject
var body: some View
{
TextField("", text: Binding<String>($project.projectName)!)
}
}
struct ProjectView_Previews: PreviewProvider
{
static var previews: some View
{
var p:Project
p = getFirstProject() //returns a Project
return ProjectView(project: p)
}
}
If I try returning the struct it says it cannot preview in the file.
If I don't return the struct I get a Function declares an opaque return type, but has no return statements in its body from which to infer an underlying type error.
UPDATE:
var app = UIApplication.shared.delegate as! AppDelegate
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
lazy var persistentContainer: NSPersistentCloudKitContainer = {
fatalError("Unresolved error \(error), \(error.userInfo)")
}
})
return container
}()
lazy var managedObjectContext: NSManagedObjectContext =
{
return persistentContainer.viewContext
}()
}
And the rest of the code:
func allRecords<T: NSManagedObject>(_ type : T.Type, sort: NSSortDescriptor? = nil) -> [T]
{
let context = app.managedObjectContext
let request = T.fetchRequest()
if let sortDescriptor = sort
{
request.sortDescriptors = [sortDescriptor]
}
do
{
let results = try context.fetch(request)
return results as! [T]
}
catch
{
print("Error with request: \(error)")
return []
}
}
func getCount() -> String
{
let r = allRecords(Project.self)
return String(r.count)
}
struct ProjectView: View
{
// #ObservedObject var project: Project
var body: some View
{
Text(getCount())
// TextField("", text: Binding<String>($project.projectName)!)
}
}
struct ProjectView_Previews: PreviewProvider
{
static var previews: some View
{
ProjectView()
}
}
r.count is returning 0, but in the main application thread it is returning 8. Has app.managedObjectContext not been defined properly? I think this has just got too complicated too quickly.
Assuming getFirstProject works correctly the following should work
struct ProjectView_Previews: PreviewProvider
{
static var previews: some View
{
ProjectView(project: getFirstProject())
}
}
However there are concerns about the following...
struct ProjectView: View
{
#State var project: Project //NSManagedObject
because #State is designed to be internal view state-only thing, but Project in your case is a model, so the recommended scenario for this is to use ObservableObject view model either by conforming Project or as standalone clue class holding Project instance(s).
I am facing issues setting up Binding in the following SwiftUI code snippet. I am trying this on xCode Beta 7 ((11M392r).
In the code snippet below, I am creating 2 Stepper views.
If I pass $student.totalMarks to Stepper, it works and creates the right Binding.
But if I try to access $student.marks.score1, that does not work and shows the following compilation error:
Generic parameter 'Subject' could not be inferred.
Is there a way to pass single field from a nested property into a binding?
struct Marks {
public let score1: Int
public let score2: Int
public let score3: Int
}
class Student: ObservableObject {
#Published var totalMarks: Int = 145
#Published var marks = Marks(score1: 67, score2: 56, score3: 64)
}
struct ContentView: View {
#ObservedObject var student = Student()
var body: some View {
return VStack {
Stepper("Total Score: \(student.totalMarks)", value: $student.totalMarks)
Stepper("Score 1: \(student.marks.score1)", value: $student.marks.score1)
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
Easy. ;-) Don't use a constant for a stepper's value binding. Rather make your scores variables (using var instead of let in struct Marks).
I want to separate my data model from the ContentView. So I added a SwiftUI file with the following code:
import SwiftUI
import Combine
class User: BindableObject {
let willChange = PassthroughSubject<Void, Never>()
var username : String = "Jan" { willSet { willChange.send() }}
var password : String = "123456" { willSet { willChange.send() } }
var emailAddress : String = "jan#mail.nl" { willSet { willChange.send() } }
}
#if DEBUG
struct User_Previews: PreviewProvider {
static var previews: some View {
User()
.environmentObject(User())
}
}
#endif
The error I get is:
Protocol type 'Any' cannot conform to 'View' because only concrete types can conform to protocols
Error occurs in the .environmentObject(User()) line.
You don't need to use a SwiftUI File. Its a simple class which is required. Of course if you remove the code below it will work.
#if DEBUG
struct User_Previews: PreviewProvider {
static var previews: some View {
User()
.environmentObject(User())
}
}
#endif
With the new #Binding delegate and previews, I find it kinda awkward to always have to create a #State static var to create the neccesarry binding:
struct TestView: View {
#Binding var someProperty: Double
var body: some View {
//...
}
}
#if DEBUG
struct TestView_Previews : PreviewProvider {
#State static var someProperty = 0.7
static var previews: some View {
TestView(someProperty: $someProperty)
}
}
#endif
Is there a simpler way to create a binding, that proxies a simple values for testing and previewing?
You can use .constant(VALUE) in your Previews, no need to create a #State.
/// A value and a means to mutate it.
#propertyWrapper public struct Binding<Value> {
/// Creates a binding with an immutable `value`.
public static func constant(_ value: Value) -> Binding<Value>
}
e.g.
TestView(someProperty: .constant(5.0))
I wrote about this in depth here, but the short version is simply to add the following extension:
extension Binding {
public static func variable(_ value: Value) -> Binding<Value> {
var state = value
return Binding<Value> {
state
} set: {
state = $0
}
}
}
And then do...
struct TestView_Previews : PreviewProvider {
static var previews: some View {
TestView(someProperty: .variable(0.7)
}
}