I'm very new to SwiftUI and Xcode and very less knowledge in Class, Stuct and function() in Swift. I'm trying to put my code inside to ContentView View{}. Because I couldn't see the result of this code on the (UI)user interface. Also, I couldn't find a good article on how this code runs in Sturct ContnetView.Here is the code I want to run.
import Cocoa
let options = CGWindowListOption(arrayLiteral: .excludeDesktopElements, .optionOnScreenOnly)
let windowsListInfo = CGWindowListCopyWindowInfo(options, CGWindowID(0))
let infoList = windowsListInfo as! [[String:Any]]
let visibleWindows = infoList.filter{ $0["kCGWindowLayer"] as! Int == 0 }
print(visibleWindows)
ContentView looks like,
import SwiftUI
struct ContentView: View {
var body: some View {
Text("Hello, world!")
.padding()
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
When I put code inside to View{}. It gives errors.
Think of your SwiftUI code like "View Builder Code". The first snippet you provided is "Logical Code".
Your SwiftUI View Builder code is intended to describe your view. When you add logical code in there, SwiftUI wouldn't know when to run it.
For instance, you can tell SwiftUI to run the code when the view appears using the .onAppear modifier.
Text("Hello, world!")
.padding()
.onAppear {
// Your Logical Code
let options = CGWindowListOption(arrayLiteral: .excludeDesktopElements, .optionOnScreenOnly)
let windowsListInfo = CGWindowListCopyWindowInfo(options, CGWindowID(0))
let infoList = windowsListInfo as! [[String:Any]]
let visibleWindows = infoList.filter{ $0["kCGWindowLayer"] as! Int == 0 }
print(visibleWindows)
}
For your use case, you might want to consider creating a #State variable with an array of visibleWindows or even a computed property.
Related
I am relatively new to SwiftUI and I'm trying to work on my first app. I am trying to use a segmented picker to give the user an option of changing between a DayView and a Week View. In each on of those Views, there would be specific user data that whould be shown as a graph. The issue I am having is loading the data. I posted the code below, but from what I can see, the issue comes down to the following:
When the view loads in, it starts with loading the dayView, since the selectedTimeInterval = 0. Which is fine, but then when the users presses on the "Week" in the segmented Picker, the data does not display. This due to the rest of the View loading prior to the .onChange() function from the segmented picker running. Since the .onChange is what puts the call into the viewModel to load the new data, there is no data. You can see this in the print statements if you run the code below.
I would have thought that the view load order would have been
load segmented picker
run the .onChange if the value changed
load the rest of the view
but the order actual is
load segmented picker,
load the rest of the view (graph loads with no data here!!!!!)
run the .onChange if the value has changed.
I am pretty lost so any help would be great! Thank you so much!
import SwiftUI
import OrderedCollections
class ViewModel: ObservableObject{
#Published var testDictionary: OrderedDictionary<String, Int> = ["":0]
public func daySelected() {
testDictionary = ["Day View Data": 100]
}
public func weekSelected() {
testDictionary = ["Week View Data": 200]
}
}
struct ContentView: View {
#State private var selectedTimeInterval = 0
#StateObject private var vm = ViewModel()
var body: some View {
VStack {
Picker("Selected Date", selection: $selectedTimeInterval) {
Text("Day").tag(0)
Text("Week").tag(1)
}
.pickerStyle(SegmentedPickerStyle())
.onChange(of: selectedTimeInterval) { _ in
let _ = print("In on change")
//Logic to handle different presses of the selector
switch selectedTimeInterval {
case 0:
vm.daySelected()
case 1:
vm.weekSelected()
default:
print("Unknown Selected Case")
}
}
switch selectedTimeInterval {
case 0:
let _ = print("In view change")
Day_View()
case 1:
let _ = print("In view change")
Week_View(inputDictionary: vm.testDictionary)
default:
Text("Whoops")
}
}
}
}
struct Day_View: View {
var body: some View {
Text("Day View!")
}
}
struct Week_View: View {
#State private var inputDictionary: OrderedDictionary<String,Int>
init(inputDictionary: OrderedDictionary<String,Int>) {
self.inputDictionary = inputDictionary
}
var body: some View {
let keys = Array(inputDictionary.keys)
let values = Array(inputDictionary.values)
VStack {
Text(keys[0])
Text(String(values[0]))
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
In your WeekView, change
#State private var inputDictionary: OrderedDictionary<String,Int>
to
private let inputDictionary: OrderedDictionary<String,Int>
#State is for the local state of the view. The idea is that you are initing it with initial state and from then on the view itself will change it and cause re-renders. When the WeekView is re-rendered, SwiftUI is ignoring the parameter you pass into the init and copying it from the previous WeekView to maintain state.
But, you want to keep passing in the dictionary from ContentView and cause re-renders from the parent view.
The main issue is that the initialization of the #State property wrapper is wrong.
You must use this syntax
#State private var inputDictionary: OrderedDictionary<String,Int>
init(inputDictionary: OrderedDictionary<String,Int>) {
_inputDictionary = State(wrappedValue: inputDictionary)
}
Or – if inputDictionary is not going to be modified – just declare it as (non-private) constant and remove the init method
let inputDictionary: OrderedDictionary<String,Int>
I am trying to add UI tests to a SwiftUI project.
I have a list, which contains views - those then contain a number of views.
I cannot seem to access the furthest most view in my UI test.
I thought I could add an accessibility identifier to each element but I cannot make my test pass still.
A very simple example;
ContentView
struct ListModel: Identifiable {
let id: String
let text: String
}
struct ContentView: View {
private var state = (0..<50).map { ListModel(id: "\($0)", text: "Row \($0)") }
var body: some View {
List(state, id: \.id) { item in
ContentViewRow(text: item.text)
.accessibility(identifier: "FEED_ITEM")
}
.accessibility(identifier: "FEED")
}
}
struct ContentViewRow: View {
let text: String
var body: some View {
Text(text)
.accessibility(identifier: "CONTENT_ROW_TEXT")
}
}
Tests
class TestingSwiftUIUITests: XCTestCase {
func testExample() throws {
// UI tests must launch the application that they test.
let app = XCUIApplication()
app.launch()
let feed = app.tables["FEED"]
XCTAssert(feed.waitForExistence(timeout: 0.5))
let row0 = feed.staticTexts["FEED_ITEM"].firstMatch
XCTAssert(row0.waitForExistence(timeout: 0.5))
let textView = row0.staticTexts["CONTENT_ROW_TEXT"].firstMatch
XCTAssert(textView.waitForExistence(timeout: 0.5)) // <-- This fails.
}
}
How can I access a view inside ContentViewRow - thank you.
identifier: "CONTENT_ROW_TEXT" seems to be overriden by identifier: "FEED_ITEM"
Perhaps you can leave just "FEED_ITEM" and check for the label text if needed.
let row0 = feed.staticTexts["FEED_ITEM"].firstMatch
XCTAssert(row0.waitForExistence(timeout: 0.5))
XCTAssert(row0.label == "Row 0")
I get nil error since I changed my project to use 'objectWillChange.send()'.
Because I needed to update the view manually since it doesn't update itself for custom carousel view(because of core data?).
If I write "1" and tap the next button(NavigationLink)
Console log
page.name: 1
catch error: nilError
Here is the codes.
AddPage Class
class AddPage: ObservableObject {
#Environment(\.presentationMode) var presentation
#Environment(\.managedObjectContext) var moc
func addPage(item: String) {
if item != "" {
let page = Page(context: self.moc)
page.name = item
do {
print("page.name: \(page.name ?? "Unknown")")
try self.moc.save()
} catch {
print("catch: \(error)")
}
objectWillChange.send()
presentation.wrappedValue.dismiss()
}
}
}
AddPageView
#ObservedObject var add = AddPage()
NavigationLink(destination: ContentView()){
Text("Next")
.background(
Rectangle()
.frame(width: 330, height: 60, alignment: .center)
)
.onTapGesture {
add.addPage(item: pageName)
}
}
SceneDelegate Class
added only
var addpage = AddPage()
let contentView = ContentView()
.environment(\.managedObjectContext, context)
.environmentObject(addpage)
Can someone please help me..?
Change
#Environment(\.managedObjectContext) var moc
To something like (If you are using the sample code that Xcode generates) if not manually get your moc however you set it up
var moc = PersistentController.shared.container.viewContext
#Environment wrappers are inconsistent in a class they should only be used in a SwiftUI View.
If you don't have a PersistentController get the code from a brand new project with SwiftUI lifecycle and CoreData. Be CAREFUL and keep the name from the original stack it is usually the app name
Comment out (DONT delete until you are sure this works) the stack code from the SceneDelegate and
make a variable
let context = PersistentController.shared.container.viewContext
So you can keep the context in your View
I have a NavigationView with a list showing tasks from a CoreData FetchRequest. The FetchRequest is sorted ascending on Task.dueDate. The TaskDetail view basically consists of a TextField for the title and a date picker for the date. Changing the values in the detail view works. Though I get some weird behaviour every time I try to change the date value. The date gets changed but the Navigation view automatically exits the detail view and goes back to the list view. It only happens when I change the date in such a way that the list gets rearranged due to the sorting.
How do I prevent this weird behaviour described above??
//
// ContentView.swift
import SwiftUI
import CoreData
struct ContentView: View {
#Environment(\.managedObjectContext) var moc
#FetchRequest(fetchRequest: Task.requestAllTasks()) var tasks: FetchedResults<Task>
var body: some View {
NavigationView {
List(tasks, id: \.id) { task in
NavigationLink(destination: TaskDetail(task: task)) {
Text("\(task.title)")
}
}.navigationBarTitle("Tasks").navigationBarItems(trailing: Button("new") {self.addTask()})
}
}
func addTask() -> Void {
let newTask = Task(context: self.moc)
newTask.id = UUID()
newTask.title = "task \(tasks.count)"
newTask.dueDate = Date()
print("created new Task")
if (self.moc.hasChanges) {
try? self.moc.save()
print("saved MOC")
}
print(self.tasks)
}
}
struct TaskDetail : View {
#ObservedObject var task: Task
var body: some View {
VStack{
TextField("name", text: $task.title)
DatePicker("dueDate", selection: $task.dueDate, displayedComponents: .date)
.labelsHidden()
}
}
}
//
// Task.swift
import Foundation
import CoreData
public class Task: NSManagedObject, Identifiable {
#NSManaged public var id: UUID?
#NSManaged public var dueDate: Date
#NSManaged public var title: String
static func requestAllTasks() -> NSFetchRequest<Task> {
let request: NSFetchRequest<Task> = Task.fetchRequest() as! NSFetchRequest<Task>
let sortDescriptor = NSSortDescriptor(key: "dueDate", ascending: true)
request.sortDescriptors = [sortDescriptor]
return request
}
}
To create a running minimal reproducible version of this...do:
Create new Xcode "Single View App" Project. Make sure to check the
CoreData checkbox.
Copy the code for ContentView above and paste/replace in ContentView.swift.
Create a new Swift file named Task. Copy the code for Task and paste in Task.swift.
Add the entities in the ProjectName.xcdatamodeld according to the image below.
Run
I am on Xcode 11.4.
Let me know if you need me to provide more information.
Any help is much appreciated! Thanks!
UPDATE 2 (iOS 14 beta 3)
The issue seems to be fixed in iOS 14 beta 3: the Detail view does no longer pop when making changes that affect the sort order.
UPDATE
It seems Apple sees this as a feature, not a bug; today they replied to my feedback (FB7651251) about this issue as follows:
We would recommend using isActive and managing the push yourself using
the selection binding if this is the behavior you desire. As is this
is behaving correctly.
This is because the identity of the pushed view changes when you
change the sort order.
As mentioned in my comment above I believe this is a bug in iOS 13.4.
A workaround could be to use a NavigationLink outside of the List and define the List rows as Buttons that
a) set the task to be edited (a new #State var selectedTask) and
b) trigger the NavigationLink to TaskDetail(task: selectedTask!).
This setup will uncouple the selected task from its position in the sorted list thus avoiding the misbehaviour caused by the re-sort potentially caused by editing the dueDate.
To achieve this:
add these two #State variables to struct ContentView
#State private var selectedTask: Task?
#State private var linkIsActive = false
update the body of struct ContentView as follows
var body: some View {
NavigationView {
ZStack {
NavigationLink(
destination: linkDestination(selectedTask: selectedTask),
isActive: self.$linkIsActive) {
EmptyView()
}
List(tasks) { task in
Button(action: {
self.selectedTask = task
self.linkIsActive = true
}) {
NavigationLink(destination: EmptyView()){
Text("\(task.title)")
}
}
}
}
.navigationBarTitle("Tasks").navigationBarItems(trailing: Button("new") {self.addTask()})
}
}
add the following struct to ContentView.swift
struct linkDestination: View {
let selectedTask: Task?
var body: some View {
return Group {
if selectedTask != nil {
TaskDetail(task: selectedTask!)
} else {
EmptyView()
}
}
}
}
I encountered the same problem and could not find a good solution.
But I have another workaround.
I made the Fetchrequest dynamic and changed the sortdescriptor, when the link is active.
This has the negative sideeffect that the list sorts itself with an animation every time you navigate back to the ContentView.
If you add the following struct for your list:
struct TaskList: View {
#Environment(\.managedObjectContext) var moc
#FetchRequest var tasks: FetchedResults<Task>
#Binding var activeTaskLink: Int?
init(activeTaskLink: Binding<Int?>, currentSortKey: String) {
self._activeTaskLink = activeTaskLink
self._tasks = Task.requestAllTask(withSortKey: currentSortKey)
}
var body: some View {
List(tasks, id: \.id) { task in
NavigationLink(destination: TaskDetail(task: task), tag: task.objectId.hashValue, selection: self.$activeTaskLink) {
Text("\(task.title)")
}
}
}
}
Then change the requestAllTask function in Task.swift:
static func requestAllTasks(withSortKey key: String) -> NSFetchRequest<Task> {
let request: NSFetchRequest<Task> = Task.fetchRequest() as! NSFetchRequest<Task>
let sortDescriptor = NSSortDescriptor(key: key, ascending: true)
request.sortDescriptors = [sortDescriptor]
return request
}
Then add a state for the activeTask in the ContentView
#State var activeTaskLink: Int? = nil
and change the body to
var body: some View {
TaskList(activeTaskLink: self.$activeTaskLink, currentSortKey: self.activeNavLink != nil ? "id" : "dueDate")
.navigationBarTitle("Tasks")
.navigationBarItems(trailing: Button("new") {self.addTask()})
}
Quick question: what exactly is the body variable? If I am correct in my interpretation of Swift, it is initialized as an object of View or one of its subclasses. However, inside its initialization, there seem to be a series of constructor calls (such as Text() or Image()) that are not stored in any variable or returned. I am new to Swift coming from Java and I was not able to find much about this online. Could someone please explain?
Also, same for VStack and HStack--are they collections of objects?
import SwiftUI
struct ContentView: View {
var body: some View {
VStack {
Text("Hello SwiftUI!")
}
}
}
struct ContentView_Preview: PreviewProvider {
static var previews: some View {
ContentView()
}
}
View is a protocol, not a class. The type of body is some View, which is an opaque result type, meaning it returns some particular concrete type that implements View, but that concrete type isn't exposed to the public as part of the API.
SwiftUI uses Swift's the function builder syntax, though I'm not sure how you could ever figure that out yourself without first reading it from somewhere else.
Further reading: https://www.swiftbysundell.com/articles/the-swift-51-features-that-power-swiftuis-api/
it is easy to check. try the next in your Playground
import SwiftUI
import PlaygroundSupport
struct ContentView: View {
var body: some View {
VStack {
Text("Hello SwiftUI!")
}
}
}
let cv = ContentView()
print("type of ContentView.body:", type(of: cv.body))
PlaygroundPage.current.setLiveView(cv)
it prints
type of ContentView.body: VStack<Text>
Even though View is a protocol, the struct adopting it MUST HAVE A CONCRETE TYPE.
it could be written as
import SwiftUI
import PlaygroundSupport
struct ContentView: View {
var body: some View {
let t = Text("Hello World!")
let content: ()-> Text = {
return t
}
let vs = VStack(alignment: .center, spacing: 0, content: content)
return vs
}
}
let cv = ContentView()
print("type of ContentView.body:", type(of: cv.body))
PlaygroundPage.current.setLiveView(cv)
with the same result .-)
DON'T TRY TO DEFINE A CLASS CONFORMING TO VIEW PROTOCOL!
this code will compile without error, but it will crash ... Yes, there is a lot to improve, I expect that the compilation will fail.
import SwiftUI
import PlaygroundSupport
final class ContentView: View {
var body: some View {
let t = Text("Hello World!")
let content: ()-> Text = {
return t
}
let vs = VStack(alignment: .center, spacing: 0, content: content)
return vs
}
}
let cv = ContentView()
print("type of ContentView.body:", type(of: cv.body))
PlaygroundPage.current.setLiveView(cv)