import SwiftUI
GuideImageView
currentPage mean 1VStack which is in text and images from array from guidelists
struct GuideImageView: View {
#State var currentPage: Int = 0
var body: some View {
VStack{
TabView(selection: $currentPage){
ForEach(guidelists){i in
VStack{
Text(i.explain)
Image(i.image)
.resizable()
}
}
}.tabViewStyle(PageTabViewStyle(indexDisplayMode: .never)) //page처럼 구현 + ...을 안보이게함
Button("Next") {
if currentPage == 3 {
currentPage = 0
//return
}else{
currentPage += 1
}
}
}
}
}
Struct GuideList
struct GuideList: Identifiable, Hashable{//가이드리스트 구조체, 이미지와 설명넣기
let id = UUID() //UUID = 고유식별자
let image: String
let explain: String
}
let guidelists
let guidelists = [
GuideList(image: "image1",explain: "explain1." ),
GuideList(image: "image2",explain: "explain2." ),
GuideList(image: "image3",explain: "explain3." )
]
GuideImageView_Previews
struct ImageView_Previews: PreviewProvider {
static var previews: some View {
GuideImageView()
}
}
I want to make button to go to next page
button seems doesn't work
The reason for this not working is the type mismatch in your models id and the selection var.
Detail:
TabView(selection: $currentPage){
ForEach(guidelists){i in
these two lines tell the compiler that the id for every element is of type UUID (because GuideList is identifieable and id is of type UUID. Thats fine for itself, but TabView has a selection var of type Int (currentPage is an Int) so it is not working. So changing one of both types to equal the other will solve the problem.
easy example:
Change your code to:
struct GuideList: Identifiable, Hashable{//가이드리스트 구조체, 이미지와 설명넣기
let id: Int
let image: String
let explain: String
}
let guidelists = [
GuideList(id: 0, image: "image1",explain: "explain1."),
GuideList(id: 1, image: "image2",explain: "explain2." ),
GuideList(id: 2, image: "image3",explain: "explain3." )
]
Related
In my content view I have a home page with some text that says "Welcome, xxxx" where xxxx is the name fetched from a firebase database. This field can be changed in the settings page that is navigated to via a Navigation Link. When the name is changed and saved the name on the home page only updates when you force shutdown the app. How do I force update the view when you press the back button from settings.
This is how I display the field:
Text("Welcome, \(companyName)")
.font(.system(size: 23))
.bold()
.foregroundColor(Color("background"))
.padding(.bottom, 50)
This is how I set a value to companyName:
func SetData() {
var db = Firestore.firestore()
let user = Auth.auth().currentUser
let userName = user?.email ?? ""
let docRef = db.collection("CONTACT").document(userName)
docRef.getDocument { (document, error) in
if let document = document, document.exists {
//Setting Values
let data = document.data()
self.companyName = data?["companyName"] as? String ?? ""
} else {
print("Document does not exist")
}
}
}
There are several solutions to this, but you haven't provided enough code outlining what you have done to modify the variable companyName. The easiest solution would be to pass companyName as a binding value into the settings.
What I imagine here is that your HomeView is fetching the data on launch. In the settings, a change data request is made, but nothing is done to update the data in the HomeView. By using a binding variable we can ensure that the companyName connects to the source of truth in the HomeView, and so the function modifies the companyName which is precisely the company name on the HomeView vs. modifying potentially the value of companyName.
struct HomeView: View {
#State var companyName = "Microsoft"
var body: some View {
NavigationView {
NavigationLink(destination: SettingsView(companyName: $companyName)) {
Text("Tap to navigate to Settings")
}
}
}
}
struct SettingsView: View {
#Binding var companyName : String
var body: some View {
Button {
SetData()
} label: {
HStack {
Text("Tap to change!")
Text("\(companyName)!")
}
}
}
func SetData() {
var db = Firestore.firestore()
let user = Auth.auth().currentUser
let userName = user?.email ?? ""
let docRef = db.collection("CONTACT").document(userName)
docRef.getDocument { (document, error) in
if let document = document, document.exists {
//Setting Values
let data = document.data()
self.companyName = data?["companyName"] as? String ?? ""
} else {
print("Document does not exist")
}
}
}
}
If you have already done this at it doesn't somehow work, another solution is to add an .onAppear modifier to your HomeView.
struct HomeView: View {
#State var companyName = "Microsoft"
var body: some View {
VStack {
// code ...
}
.onAppear {
fetchData()
}
}
func fetchData() {
// code that returns companyFetchedName
self.companyName = companyFetchedName
}
}
Modify it on main queue, like
docRef.getDocument { (document, error) in
if let document = document, document.exists {
//Setting Values
let data = document.data()
DispatchQueue.main.async { // << here !!
self.companyName = data?["companyName"] as? String ?? ""
}
} else {
print("Document does not exist")
}
}
I'm loading data in from my Firebase backend, the "lazy" part makes my app look glitchy/frozen-like when scrolling down, it lags heavily...
Is it possible to create a VGrid "without the lazy functionality"??
(iOS 14)
If not, any suggestions other than ditching the Grid look altogether?
let layout = [
GridItem(.flexible()),
GridItem(.flexible()),
]
#ObservedObject var homeModel = Home_ViewModel()
NavigationView(content: {
ScrollView() {
LazyVGrid(columns: layout, spacing: 10) {
ForEach(homeModel.projectList) { item in
ProjectItemWidget(
projectID: item.id,
projectTitle: item.projectTitle,
projectAuthorProfileImage: item.authorProfileImageUrl,
projectAuthor: item.projectAuthor)
}
}
.padding(.trailing, 7.5)
}
}
I was struggling thinking in a solution where a could create a grid layout without using LazyVGrid and came up with the following:
extension Array {
func getElementAt(index: Int) -> Element? {
return (index < self.endIndex) ? self[index] : nil
}
}
struct CustomGridLayout<Element, GridCell>: View where GridCell: View {
private var array: [Element]
private var numberOfColumns: Int
private var gridCell: (_ element: Element) -> GridCell
init(_ array: [Element], numberOfColumns: Int, #ViewBuilder gridCell: #escaping (_ element: Element) -> GridCell) {
self.array = array
self.numberOfColumns = numberOfColumns
self.gridCell = gridCell
}
var body: some View {
Grid {
ForEach(Array(stride(from: 0, to: self.array.count, by: self.numberOfColumns)), id: \.self) { index in
GridRow {
ForEach(0..<self.numberOfColumns, id: \.self) { j in
if let element = self.array.getElementAt(index: index + j) {
self.gridCell(element)
}
}
}
}
}
.frame(maxWidth: .infinity, maxHeight: .infinity)
}
}
An example using in a View:
struct ContentView: View {
private var array: [Int] = Array(1...7)
var body: some View {
CustomGridLayout(array, numberOfColumns: 3) { element in
RoundedRectangle(cornerRadius: 10)
.foregroundColor(.orange)
.overlay(alignment: .center) {
Text("\(element)")
}
}
.padding(.horizontal)
}
}
You can see the result in the following link: https://i.stack.imgur.com/1o7ip.png
I'm trying to show the data I fetched from my Firebase database. I tried creating #State var variables and add them to my function but it didn't work. I tried printing my function output in a button to print it to console and it works. I just don't know how to show them in my view my code
import SwiftUI
import Firebase
struct ProfileView: View {
var body: some View {
VStack {
Button(action: {
profilef()
}) {
Text("hello")
}
HStack {
Button(action: {
try! Auth.auth().signOut()
UserDefaults.standard.set(false, forKey: "status")
NotificationCenter.default.post(name: NSNotification.Name("statusChange"), object: nil)
}) {
Text("Logout")
}
}
}
}
func profilef() {
let userID = Auth.auth().currentUser?.uid
let ref = Database.database().reference()
ref.child("UserInfo").child(userID!).observeSingleEvent(of: .value, with: { (snapshot) in
// Get user value
let value = snapshot.value as? [String : AnyObject]
let name = value?["fullName"] as? String ?? ""
print(name)
// ...
}) { error in
print(error.localizedDescription)
}
}
}
Just create a #State variable, which contains the name. If your function changes that variable, your view will updates.
struct profile: View {
#State var name : String = ""
var body: some View {
Text("Hello " + self.name)
And then in your function, instead of printing you will assign it to your state.
let name = value?["fullName"] as? String ?? ""
print(name)
self.name = name
That should work. I do not have an example with Firebase at the moment, so I can not test it. If it is not working, please describe the behavior.
Adding an #State property profileName and assigning it in the network request function will work after tapping the Button.
// ProfileView.swift
//
//
// Created by Shahin Bararesh on 2020-09-07.
//
import SwiftUI
import Firebase
struct ProfileView: View {
#State var profileName: String = ""
var body: some View {
VStack {
Button(action: {
profilef()
}) {
Text(profileName)
}
HStack {
Button(action: {
try! Auth.auth().signOut()
UserDefaults.standard.set(false, forKey: "status")
NotificationCenter.default.post(name: NSNotification.Name("statusChange"), object: nil)
}) {
Text("Logout")
}
}
}
}
func profilef() {
let userID = Auth.auth().currentUser?.uid
let ref = Database.database().reference()
ref.child("UserInfo").child(userID!).observeSingleEvent(of: .value, with: { (snapshot) in
// Get user value
let value = snapshot.value as? [String : AnyObject]
let name = value?["fullName"] as? String ?? ""
self.profileName = name
// ...
}) { error in
print(error.localizedDescription)
}
}
}
Scenario: Attempting to broadcast a variable value via an ObservableObject.
Problem: I'm only getting the default value; not the assigned value.
Here's the origin.
Button #1 starts a function to get data.
Button #2 retrieves the ObservedObject's revised value
I removed some of the vestigial code to make the presentation simpler:
struct ContentView: View {
#ObservedObject var networkManager = NetworkManager()
let fontCustom = Font.custom("Noteworthy", size: 23.0)
var body: some View {
ZStack {
// ...
// ...
HStack {
Button(
action: {
NetworkManager().getCalculatorIDs()
},
label: {
Text("1")
}
)
Button(
action: {
self.calculator.calculate("2");
print(self.networkManager.calculationID) // stop and check.
},
label: { Text("2") }
)
// ...
// ...
}
}
So I tap Button #1 then tap Button #2 to check if the ObservedObject has the generated id value.
I'm expecting an alphanumeric id value in the print().
Instead, I got the original value:
Royal Turkey
(lldb)
Here's the ObservableObject:
struct CalculationIdentifier: Decodable {
let id: String
let tokens: [String]
}
class NetworkManager: ObservableObject {
#Published var calculationID = "Royal Turkey"
#Published var isAlert = false
#Published var name = "Ric Lee"
let calculations = "https://calculator-frontend-challenge.herokuapp.com/Calculations"
func getCalculatorIDs() {
let urlRequest = URLRequest(url: URL(string: calculations)!)
let configuration = URLSessionConfiguration.ephemeral
let task = URLSession(configuration: configuration).dataTask(with: urlRequest) { data, _, error in
DispatchQueue.main.async {
do {
let result = try JSONDecoder().decode([CalculationIdentifier].self, from: data!)
if !result.isEmpty {
self.calculationID = (result[0] as CalculationIdentifier).id
print("Inside do{}. result = \(result)")
self.isAlert = true
} else {
print(#function, "Line:", #line, ": No Result")
}
} catch {
print(error)
}
}
}
task.resume()
}
}
BTW: Here's the local console output, the string value of 'id' should have been passed to the host as an ObservedObject value:
Inside do{}. result = [RicCalculator2.CalculationIdentifier(id: "d3dd3b1e-d9f6-4593-8c85-b8fd3d018383", tokens: [])]
So I do have a bona fide id value to send.
Why only the original value?
What am I missing?
...do I need to do a 'send' or something?
This
A. #ObservedObject var networkManager = NetworkManager()
and this
B. NetworkManager().getCalculatorIDs()
in your code are different objects, ie. you create one object as member, then other object on the stack, which does something, and then ask first object to return something - naturally if returns what it has on initialise.
Probably you assumed in case B
self.networkManager.getCalculatorIDs()
I'm struggling with this Swift code already for some time and do not find the problem. The code
below should provide the File Directory as DataSource for a NSOutlineView. The GUI is quite simple
just a window with a NSOutlineView and a Object for the OutlineViewController instance.
When I start the application it shows the root entry, when I expand the root entry it shows for a short period the sub items. Then the application crashes with an Error in file "main.swift" at line "NSApplicationMain(C_ARGC, C_ARGV) --> "EXC_BAD_ACCESS(code=EXC_I386_GPFLT)" ?
If added some println() to proof the directory structure - this seems to be fine.
The swift code:
import Cocoa
import Foundation
class FileSystemItem {
let propertyKeys = [NSURLLocalizedNameKey, NSURLEffectiveIconKey, NSURLIsPackageKey, NSURLIsDirectoryKey,NSURLTypeIdentifierKey]
let fileURL: NSURL
var name: String! {
let resourceValues = fileURL.resourceValuesForKeys([NSURLNameKey], error: nil)
return resourceValues[NSURLNameKey] as? NSString
}
var localizedName: String! {
let resourceValues = fileURL.resourceValuesForKeys([NSURLLocalizedNameKey], error: nil)
return resourceValues[NSURLLocalizedNameKey] as? NSString
}
var icon: NSImage! {
let resourceValues = fileURL.resourceValuesForKeys([NSURLEffectiveIconKey], error: nil)
return resourceValues[NSURLEffectiveIconKey] as? NSImage
}
var dateOfCreation: NSDate! {
let resourceValues = self.fileURL.resourceValuesForKeys([NSURLCreationDateKey], error: nil)
return resourceValues[NSURLCreationDateKey] as? NSDate
}
var dateOfLastModification: NSDate! {
let resourceValues = fileURL.resourceValuesForKeys([NSURLContentModificationDateKey], error: nil)
return resourceValues[NSURLContentModificationDateKey] as? NSDate
}
var typeIdentifier: String! {
let resourceValues = fileURL.resourceValuesForKeys([NSURLTypeIdentifierKey], error: nil)
return resourceValues[NSURLTypeIdentifierKey] as? NSString
}
var isDirectory: String! {
let resourceValues = fileURL.resourceValuesForKeys([NSURLIsDirectoryKey], error: nil)
return resourceValues[NSURLIsDirectoryKey] as? NSString
}
var children: [FileSystemItem] {
var childs: [FileSystemItem] = []
var isDirectory: ObjCBool = ObjCBool(1)
let fileManager = NSFileManager.defaultManager()
var checkValidation = NSFileManager.defaultManager()
if (checkValidation.fileExistsAtPath(fileURL.relativePath)) {
if let itemURLs = fileManager.contentsOfDirectoryAtURL(fileURL, includingPropertiesForKeys:propertyKeys, options:.SkipsHiddenFiles, error:nil) {
for fsItemURL in itemURLs as [NSURL] {
if (fileManager.fileExistsAtPath(fsItemURL.relativePath, isDirectory: &isDirectory))
{
if(isDirectory == true) {
let checkItem = FileSystemItem(fileURL: fsItemURL)
childs.append(checkItem)
}
}
}
}
}
return childs
}
init (fileURL: NSURL) {
self.fileURL = fileURL
}
func hasChildren() -> Bool {
return self.children.count > 0
}
}
class OutlineViewController : NSObject, NSOutlineViewDataSource {
let rootFolder : String = "/"
let rootfsItem : FileSystemItem
let fsItemURL : NSURL
let propertyKeys = [NSURLLocalizedNameKey, NSURLEffectiveIconKey, NSURLIsPackageKey, NSURLIsDirectoryKey,NSURLTypeIdentifierKey]
init() {
self.fsItemURL = NSURL.fileURLWithPath(rootFolder)
self.rootfsItem = FileSystemItem(fileURL: fsItemURL)
for fsItem in rootfsItem.children as [FileSystemItem] {
for fsSubItem in fsItem.children as [FileSystemItem] {
println("\(fsItem.name) - \(fsSubItem.name)")
}
}
}
func outlineView(outlineView: NSOutlineView!, numberOfChildrenOfItem item: AnyObject!) -> Int {
if let theItem: AnyObject = item {
let tmpfsItem: FileSystemItem = item as FileSystemItem
return tmpfsItem.children.count
}
return 1
}
func outlineView(outlineView: NSOutlineView!, isItemExpandable item: AnyObject!) -> Bool {
if let theItem: AnyObject = item {
let tmpfsItem: FileSystemItem = item as FileSystemItem
return tmpfsItem.hasChildren()
}
return false
}
func outlineView(outlineView: NSOutlineView!, child index: Int, ofItem item: AnyObject!) -> AnyObject! {
if let theItem: AnyObject = item {
let tmpfsItem: FileSystemItem = item as FileSystemItem
return tmpfsItem.children[index]
}
return rootfsItem
}
func outlineView(outlineView: NSOutlineView!, objectValueForTableColumn tableColumn: NSTableColumn!, byItem item: AnyObject!) -> AnyObject! {
if let theItem: AnyObject = item {
let tmpfsItem: FileSystemItem = item as FileSystemItem
return tmpfsItem.localizedName
}
return "-empty-"
}
}
class AppDelegate: NSObject, NSApplicationDelegate {
#IBOutlet var window: NSWindow
func applicationDidFinishLaunching(aNotification: NSNotification?) {
// Insert code here to initialize your application
}
func applicationWillTerminate(aNotification: NSNotification?) {
// Insert code here to tear down your application
}
}
Any hints ?
I had a similar problem with EXC_BAD_ACCESS on an NSOutlineView - with an NSOutlineViewDataSource. The same behaviour of as soon as the node was expanded, the data was displayed then the crash occurred. Some profiling in instruments showed that somewhere a Zombie object was created, and then the Outline view tried to access it.
I think this is a bug - but I managed to get around it by changing all Swift 'Strings' to 'NSStrings'. This may have to be done for all Swift types if you are using them.
In order to ensure everything was an NSString, I had to declare constants within the class such as:
var empty_string : NSString = ""
Because anytime I fed it a Swift string all hell broke loose. Oh well hopefully this will be fixed in the future!
So, just to clarify what is going on. NSOutlineView does not retain objects that it is given for its "model"; it was always expected that the client would retain them. For ARC code, this doesn't work well, because if you return a new instance to the NSOutlineView methods the object will not be retained by anything and will quickly be freed. Then subsequent outlineView delegate methods the touch these objects will lead to crashes. The solution to that is to retain the objects yourself in your own array.
Note that the objects returned from objectValueForTableColumn are retained by the NSControl's objectValue.
Back to Swift: As Thomas noted the objects have to be objc objects since they are bridged to an objc class. A Swift string is implicitly bridged to a temporary NSString. This leads to a crash because of the above issue, since nothing retains the NSString instance. That is why maintaining an array of NSStrings "solves" this problem.
The solution would be for NSOutlineView to have an option to retain the items given to it. Please consider logging a bug request for it to do this through bugreporter.apple.com
Thanks,
corbin (I work on NSOutlineView)
It seems that
outlineView(outlineView: NSOutlineView!, objectValueForTableColumn tableColumn: NSTableColumn!, byItem item: AnyObject!) -> AnyObject!
needs to return an object that conforms to obj-c protocol. So you can return
#objc class MyClass {
...
}
(or NSString and the like). But not native Swift stuff like String or Array etc.
I believe one of the problems going on here is the fact that the "children" array is getting replaced every time the children property is accessed.
I think this causes some weak references inside the NSOutlineView to break when it queries the DataSource for information.
If you cache the "children" and access the cache to compute "numberOfChildren" and "getChildForIndex" you should see an improvement.
In Swift 3.0 I used the following code, which compiles and runs without problems. It is far away from being complete but a step in the right direction, since I am trying to translate TreeTest into Swift.
import Cocoa
import Foundation
class FileSystemItem: NSObject {
let propertyKeys: [URLResourceKey] = [.localizedNameKey, .effectiveIconKey, .isDirectoryKey, .typeIdentifierKey]
var fileURL: URL
var name: String! {
let resourceValues = try! fileURL.resourceValues(forKeys: [.nameKey])
return resourceValues.name
}
var localizedName: String! {
let resourceValues = try! fileURL.resourceValues(forKeys: [.localizedNameKey])
return resourceValues.localizedName
}
var icon: NSImage! {
let resourceValues = try! fileURL.resourceValues(forKeys: [.effectiveIconKey])
return resourceValues.effectiveIcon as? NSImage
}
var dateOfCreation: Date! {
let resourceValues = try! fileURL.resourceValues(forKeys: [.creationDateKey])
return resourceValues.creationDate
}
var dateOfLastModification: Date! {
let resourceValues = try! fileURL.resourceValues(forKeys: [.contentModificationDateKey])
return resourceValues.contentAccessDate
}
var typeIdentifier: String! {
let resourceValues = try! fileURL.resourceValues(forKeys: [.typeIdentifierKey])
return resourceValues.typeIdentifier
}
var isDirectory: Bool! {
let resourceValues = try! fileURL.resourceValues(forKeys: [.isDirectoryKey])
return resourceValues.isDirectory
}
init(url: Foundation.URL) {
self.fileURL = url
}
var children: [FileSystemItem] {
var childs: [FileSystemItem] = []
let fileManager = FileManager.default
// show no hidden Files (if you want this, comment out next line)
// let options = FileManager.DirectoryEnumerationOptions.skipsHiddenFiles
var directoryURL = ObjCBool(false)
let validURL = fileManager.fileExists(atPath: fileURL.relativePath, isDirectory: &directoryURL)
if (validURL && directoryURL.boolValue) {
// contents of directory
do {
let childURLs = try
fileManager.contentsOfDirectory(at: fileURL, includingPropertiesForKeys: propertyKeys, options: [])
for childURL in childURLs {
let child = FileSystemItem(url: childURL)
childs.append(child)
}
}
catch {
print("Unexpected error occured: \(error).")
}
}
return childs
}
func hasChildren() -> Bool {
return self.children.count > 0
}
}
class OutLineViewController: NSViewController, NSOutlineViewDelegate, NSOutlineViewDataSource {
#IBOutlet weak var outlineView: NSOutlineView!
#IBOutlet weak var pathController: NSPathControl!
var fileSystemItemURL: URL!
let propertyKeys: [URLResourceKey] = [.localizedNameKey, .effectiveIconKey, .isDirectoryKey, .typeIdentifierKey]
var rootfileSystemItem: FileSystemItem!
var rootURL: URL!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
let userDirectoryURL = URL(fileURLWithPath: NSHomeDirectory())
// directory "Pictures" is set as root
let rootURL = userDirectoryURL.appendingPathComponent("Pictures", isDirectory: true)
self.pathController.url = rootURL
self.rootfileSystemItem = FileSystemItem(url: rootURL)
for fileSystemItem in rootfileSystemItem.children as [FileSystemItem] {
for subItem in fileSystemItem.children as [FileSystemItem] {
print("\(fileSystemItem.name) - \(subItem.name)")
}
}
//FileSystemItem.rootItemWithPath(self.pathControl.URL.path)
//self.searchForFilesInDirectory(picturesPath)
}
override var representedObject: Any? {
didSet {
// Update the view, if already loaded.
}
}
#IBAction func pathControllerAction(_ sender: NSPathControl) {
print("controller clicked")
}
// MARK: - outline data source methods
func outlineView(_ outlineView: NSOutlineView, numberOfChildrenOfItem item: Any?) -> Int {
if let fileSystemItem = item as? FileSystemItem {
return fileSystemItem.children.count
}
return 1
}
func outlineView(_ outlineView: NSOutlineView, isItemExpandable item: Any) -> Bool {
if let fileSystemItem = item as? FileSystemItem {
return fileSystemItem.hasChildren()
}
return false
}
func outlineView(_ outlineView: NSOutlineView, child index: Int, ofItem item: Any?) -> Any {
if let fileSystemItem = item as? FileSystemItem {
return fileSystemItem.children[index]
}
return rootfileSystemItem
}
func outlineView(_ outlineView: NSOutlineView, objectValueFor tableColumn: NSTableColumn?, byItem item: Any?) -> Any? {
if let fileSystemItem = item as? FileSystemItem {
switch tableColumn?.identifier {
case "tree"?:
return fileSystemItem.localizedName
case "coordinate"?:
return " empty "
default:
break
}
}
return " -empty- "
}
// MARK: - outline view delegate methods
func outlineView(_ outlineView: NSOutlineView, shouldEdit tableColumn: NSTableColumn?, item: Any) -> Bool {
return false
}
}
With a new edit the outline view now shows all files and directories. You can influence the appearance in the children section in class FileSystemItem.