I am using SwiftUI and I want to simply display array values in a Text form.
Here is my code:
ForEach(0..<returnCont().count) {
Text("\(returnCont()[$0]),")
}
I also tried this:
ForEach(returnCont().indices) {
Text("\(return()[index]),")
}
Where returnCont() is a function returning an array.
The array displays elements that are initialised, but when the array is empty and then appended through user inputs, it only displays values in the terminal, but not in the Text form on the View.
No error is displayed either, just empty text.
Try something like below-:
import SwiftUI
class Model:ObservableObject{
var dataArray = ["1","2","3"]
func returnCont() -> Int{
return dataArray.count
}
}
struct myView:View{
#ObservedObject var model = Model()
var body: some View{
ForEach(0..<model.returnCont()) { index in
Text("\(model.dataArray[index]),")
}
}
}
I don’t know how your model class looks like, so this is just a demo to give you correct idea.
Related
Long title! I do apologize.
Expected Outcome: Display uniqued string value in UILabel inside a UIView in a stackView. There may be multiple UIViews in the stackView. The stackView lives inside a tableCell. wow....
The views and cells are custom and I am not allowed to create sections. I have to work with what's in the existing codebase.
Issue I am stuck at trying to get the unique optional string values into the respective UILabels. I have a working extension to get unique items from an array. But I just don't know where to implement it, to get the unique values I need.
Code:
// Sample Model Structs
struct Parent {
var child: [Child]?
}
struct Child {
var childValue: String?
}
class TableViewCell {
var stackView = UIStackView()
func configureCellFrom(parent: Parent) {
/// Other code lives in the func to use the Parent struct.
if let child = parent.child {
if child.count > 1 {
tableCell.topLabel.text = "Multiple Child Values"
tableCell.ShowChildViewsButton.isHidden = false
for value in child {
let view = CustomUIView()
view.childValue.text = value.childValue.uniqued()
self.stackView.addArrangedSubview(view)
}
}
}
}
}
extension Sequence where Element: Hashable {
func uniqued() -> [Element] {
var set = Set<Element>()
return filter { set.insert($0).inserted }
}
}
Above Problem: Where I placed the uniqued() method, will parse out the individual characters in the string. So I know that it is one level too deep. What's the best way to achieve my required result?
The issue there is that uniqued method uses filter method declared on Sequence which will always return an array of the Sequence.Element in the case of a String an array of characters [Character]. You can simply initialize a new string with the array or characters or improve that method to support strings as well. To make that method support strings you need to extend RangeReplaceableCollection. There is a filter method declared on RangeReplaceableCollection which returns Self therefore if you filter a string it will return another string as I showed in this answer from the same post where you found the uniqued method you've shown in your question:
extension RangeReplaceableCollection where Element: Hashable {
var orderedSet: Self {
var set = Set<Element>()
return filter { set.insert($0).inserted }
}
}
Usage:
view.childValue.text = value.childValue?.orderedSet
Try this:
for (i, value) in child.enumerated() {
let view = CustomUIView()
view.childValue.text = value.childValue.uniqued()[i]
self.stackView.addArrangedSubview(view)
}
In MyController, I have the following property which is updated in other methods that are called:
#Published public var data = [Glucose]()
I also have a function, which limits this Published property by a given limit:
public func latestReadings(limit: Int = 5) -> [Glucose] {
// return latests results
}
In a SwiftUI View, I consume this data by the following, which works fine and updates when MyController's data changes:
#EnvironmentObject var data: MyController
var body: Some View {
ForEach(self.data.latestReadings(limit: 11), id: \.self) {
/// Display Text etc.
}
}
But, I want to call the following here, which converts the Glucose readings into a DataPoint array which the Chart consumes:
Chart(
data: self.data.latestReadings(limit: 37),
formattedBy: { (readings) -> [DataPoint] in
var result = [DataPoint]()
var i = 0
for reading in readings {
result.append(DataPoint(x: Double(i), y: reading.mmol))
i += 1
}
return result
}
)
...Which refers to another SwiftUI View defined as:
struct Chart: View {
// Properties
#State var data: [DataPoint] // I asusme this should be #State
var opt: ChartOptions
// Formatters
private var fmt: Formatting = Formatting.shared
// Init
public init(data: [Glucose], formattedBy:ChartDataFormatter) {
_data = State(wrappedValue: formattedBy(data)) // Again I assume this is probably wrong..
}
...draw views etc.
}
This all works on the first time the Chart is drawn, but the data property on the Chart view doesn't re-draw as the MyController data property changes. I assume I'm doing something wrong with state and observing changes here?
If I understood your workflow correctly you don't need state wrapper in Chart, because it prevents value update... so try without it, like
struct Chart: View {
// Properties
var data: [DataPoint]
// ...
// Init
public init(data: [Glucose], formattedBy:ChartDataFormatter) {
self.data = formattedBy(data)
}
// ...
#State breaks the connection with your Controller. Per the documentation #State should always be private.
Pass the data using #EnvironmentObject and manipulate it within the view or in the Controller.
I have decided after several years of development to restart my project using SwiftUI to future proof as much as I can.
In my current project I have my data in several .CSV's which I then process into dictionaries and then create a list of entries on screen using an Array of keys which are generated programmatically from user input.
All examples I've seen for SwiftUI use JSON. However the structure of these files are identical to an Array of Dictionaries. My question is; is it possible to create a Struct of a dictionary entry to pass in a forEach watching an Array of Keys (data inside the dictionary will never change and I am not looking to iterate or watch the dictionary).
My main goal is to reuse as much as possible but am willing to change what I have to get full benefit of SwiftUI. Obviously if I change the way I store my data almost everything will be useless. If there's a real benefit to converting my data to JSON or even start using something like CoreData I will.
If I'm understanding correctly, you are looking to
Take some user input
Transform that into keys that correspond to your data dictionary
Extract the data for the matching keys into some struct
Display a list of those structs using SwiftUI
Here is a simple implementation of those steps.
import SwiftUI
// A dictionary containing your data
let data: [String: Int] = [
"apples": 5,
"bananas": 3,
"cherries": 12
]
// A struct representing a match from your data
struct Item {
var name: String
var quantity: Int
}
// A view that displays the contents of your struct
struct RowView: View {
var item: Item
var body: some View {
Text("\(item.quantity) \(item.name)")
}
}
struct ContentView: View {
#State private var searchText: String = ""
func items(matching search: String) -> [Item] {
// 2 - split the user input into individual keys
let split = search.split(separator: " ", omittingEmptySubsequences: true).map { $0.lowercased() }
// 3 - turn any matching keys/values in your dictionary to a view model
return split.compactMap { name in
guard let quantity = data[name] else { return nil }
return Item(name: name, quantity: quantity)
}
}
var body: some View {
VStack {
// 1 - get user input
TextField("Search", text: $searchText)
.padding()
// 4 - display the matching values using ForEach (note that the id: \.name is important)
List {
ForEach(items(matching: searchText), id: \.name) { item in
RowView(item: item)
}
}
}
}
}
You'll see that as you type in the text field, if you enter any of the strings "apples", "bananas", or "cherries", a corresponding row will pop into your list.
Depending on the size of your list, and what kind of validation you are performing on your users search queries, you might need to be a little more careful about doing the filtering/searching in an efficient way (e.g. using Combine to only split and search after the user stops typing).
I have a minimal working example of something I'm still not sure about:
import SwiftUI
struct Car {
var name: String
}
class DataModel: ObservableObject {
#Published var cars: [Car]
init(_ cars: [Car]) {
self.cars = cars
}
}
struct TestList: View {
#EnvironmentObject var dataModel: DataModel
var body: some View {
NavigationView {
List(dataModel.cars, id: \.name) { car in
NavigationLink(destination: TestDetail(car: car).environmentObject(self.dataModel)) {
Text("\(car.name)")
}
}
}
}
}
struct TestDetail: View {
#EnvironmentObject var dataModel: DataModel
var car: Car
var carIndex: Int {
dataModel.cars.firstIndex(where: {$0.name == self.car.name})!
}
var body: some View {
Text(car.name)
.onTapGesture {
self.dataModel.cars[self.carIndex].name = "Changed Name"
}
}
}
struct TestList_Previews: PreviewProvider {
static var previews: some View {
TestList().environmentObject(DataModel([.init(name: "A"), .init(name: "B")]))
}
}
It's about the usage of structs as data models. The example is similar to the official SwiftUI tutorial by Apple (https://developer.apple.com/tutorials/swiftui/handling-user-input).
Basically, we have a DataModel class that is passed down the tree as EnvironmentObject. The class wraps the basic data types of our model. In this case, it's an array of the struct Car:
class DataModel: ObservableObject {
#Published var cars: [Car]
...
}
The example consists of a simple list that shows the names of all cars. When you tap on one, you get to a detail view. The detail view is passed the car as property (while the dataModel is passed as EnvironmentObject):
NavigationLink(destination: TestDetail(car: car).environmentObject(self.dataModel)) {
Text("\(car.name)")
}
The property car of the detail view is used to populate it. However, if you want to e.g. change the name of the car from within the detail view you have to go through the dataModel because car is just a copy of the original instance found in dataModel. Thus, you first have to find the index of the car in the dataModel's cars array and then update it:
struct TestDetail: View {
...
var carIndex: Int {
dataModel.cars.firstIndex(where: {$0.name == self.car.name})!
}
...
self.dataModel.cars[self.carIndex].name = "Changed Name"
This doesn't feel like a great solution. Searching for the index is a linear operation you have to do whenever you want to change something (the array could change at any time, so you have to constantly repeat the index search).
Also, this means that you have duplicate data. The car property of the detail view exactly mirrors the car of the viewModel. This separates the data. It doesn't feel right.
If car was a class instead of a struct, this would no be a problem because you pass the instance as reference. It would be much simpler and cleaner.
However, it seems that everyone wants to use structs for these things. Sure, they are safer, there can't be reference cycles with them but it creates redundant data and causes more expensive operations. At least, that's what it looks like to me.
I would love to understand why this might not be a problem at all and why it's actually superior to classes. I'm sure I'm just having trouble understanding this as a new concept.
This is my Realm object, basically an image with some tag attached.
class AllTags: Object {
dynamic var singleTag = ""}
class Photo: Object {
var myTags: [String] {
get {
return _backingNewTags.map { $0.singleTag }
}
set {
_backingNewTags.removeAll()
_backingNewTags.appendContentsOf(newValue.map({ AllTags(value: [$0]) }))
}
}
let _backingNewTags = List<AllTags>()
override static func ignoredProperties() -> [String] {
return ["myTags"]
}
dynamic var imagePath = ""}
I have my collectionView, I can see all my photo and when pressing an image I can see my tags, so everything is working correctly.
I have added my UISearchBar, added the txtSearchbar.delegate = self and using let data = realm.objects(AllTags).map { $0.singleTag } I can print ALL the tags inside my database.
I just need to filter in real time while I type the CollectionView cells via the UISearchBar so it shows only the images tagged with the word I'm typing. Basic.
I've been following this tutorial to filter in the collectionView - https://github.com/codepath/ios_guides/wiki/Search-Bar-Guide#example-searching-a-collection-view - After 11 hours, I can't figure out how to make it works with Realm. With hardcoded Array like the example I can make it works.
In Realm, you can filter a Results<T> based on what you're looking for. For example:
let data = realm.objects(AllTags).filter("singleTag CONTAINS %#", searchTerm)
I'm wondering, however, why you're converting your _backingNewTags to an Array [String]? Why can't you just access the tags directly? This will be much more memory & CPU efficient, and will simplify your code...