How to display information parsed from a JSON in SwiftUI [closed] - json

Closed. This question needs debugging details. It is not currently accepting answers.
Edit the question to include desired behavior, a specific problem or error, and the shortest code necessary to reproduce the problem. This will help others answer the question.
Closed 6 days ago.
Improve this question
I am using Apple's Rss, and Beyonce's top 100 album https://music.apple.com/us/album/renaissance/1630005298. I want to show the name and the artistName on the screen inside a text, but I keep getting errors. Initializer 'init(_:)' requires that 'Binding<Subject>' conform to 'StringProtocol'& Value of type 'SongViewModel' has no dynamic member 'artistName' using key path from root type 'SongViewModel'.
Below is most of my code:
import SwiftUI
struct SongView: View {
#ObservedObject var viewModel = SongViewModel()
// PREVIEW
var body: some View {
VStack (alignment: .leading) {
HStack(alignment: .top, spacing: 20.0) {
Rectangle()
.size(width: 40, height: 40)
.frame(width: 50.0, height: 40.0)
.foregroundColor(Color(.systemBlue))
// TODO: - change this to actual name
.padding(.trailing, 19.0)
VStack(alignment: .leading, spacing: 4) {
Text(viewModel.name)
.font(.headline)
.fontWeight(.heavy)
.multilineTextAlignment(.leading)
Text(viewModel.artistName)
}
Text("Explict")
Image(systemName: "heart")
}
.padding(17.0)
}
.onAppear {
Task {
await viewModel.retrieveAlbums()
}
}
}
}
struct SongView_Previews: PreviewProvider {
static var previews: some View {
SongView()
}
}
I am able to show the information in the Terminal so it is showing, but I am unable to show this info in the screen.

Related

where to place a function in SwiftUI

I am trying to write a function that puts a rectangle on the screen in a pre-existing HStack. This is the code without the function (you can see that there is some code repetition used put a few rectangles in the HStack):
struct ContentView: View {
#State var backgroundHeight = 60.0
#State var backgroundWidth = 60.0
#State var backgroundCorners = 10.0
#State var highlightHeight = 8.0
#State var highlightWidth = 8.0
#State var highlightCorners = 3.0
var body: some View {
Color.blue
.frame(width:backgroundWidth, height:backgroundHeight)
.cornerRadius(backgroundCorners)
.overlay(alignment:.center){
HStack(spacing: 2){
Rectangle()
.foregroundColor(.yellow)
.frame(width:highlightWidth, height:highlightHeight)
.cornerRadius(highlightCorners)
Rectangle()
.foregroundColor(.cyan)
.frame(width:highlightWidth, height:highlightHeight)
.cornerRadius(highlightCorners)
Rectangle()
.foregroundColor(.red)
.frame(width:highlightWidth, height:highlightHeight)
.cornerRadius(highlightCorners)
Rectangle()
.foregroundColor(.white)
.frame(width:highlightWidth, height:highlightHeight)
.cornerRadius(highlightCorners)
}
}
}
}
This text places a small rectangle on the screen with some smaller rectangles overlayed.
I then tried using the following function to streamline the code (and then calling the function in the HStack):
func quickHighlight {
Rectangle()
.foregroundColor(.yellow)
.frame(width: highlightWidth, height: highlightHeight)
.cornerRadius(highlightCorners)
}
I tried putting a variety of permutations and putting it in different parts both in and out of the code. Although the function seems to generate error messages depending on where it is placed such as 'Cannot infer contextual base...' to 'Closure containing a declaration cannot be used with result builder'. The puzzling thing is the very basic function I used as a contextual basis for this learning exercise seemed to indicate this should work (although I am sure there is something overlooked).
FYI my goal was to try a case statement with the function where the function receives an integer and then iterates through a few options to assign a colour to the rectangle.
Any help greatly appreciated.
The standard way is to make a subview. In SwiftUI small views increases performance because it tightens invalidation, i.e. it only needs to recompute the body funcs where the lets/vars have actually changed. Don't use a func that takes params to return a View because that breaks SwiftUI's change detection. A view modifier is an interesting way to make it even more reusable, I'll demonstrate both ways below:
Subview way:
struct HighlightedRectangle: View {
let color: Color
let highlightWidth, highlightHeight, highlightCorners: Float
// body only called if any of the lets are different from last time this View was init by the parent view's body.
var body: some View {
Rectangle()
.foregroundColor(color)
.frame(width: highlightWidth, height: highlightHeight)
.cornerRadius(highlightCorners)
}
}
Then use it in the parent view as follows
let colors = [.yellow, .cyan, .red, .white]
...
ForEach(colors, id: \.self) { color in {
HighlightedRectangle(color: color, highlightWidth: highlightWidth, highlightHeight: highlightHeight, highlightCorners: highlightCorners)
}
View modifier way:
struct Highlighted: ViewModifier {
let color: Color
let highlightWidth, highlightHeight, highlightCorners: Float
// body only called if any of the lets are different from last time this ViewModifier was init by the parent view's body.
func body(content: Content) -> some View {
content
.foregroundColor(color)
.frame(width: highlightWidth, height: highlightHeight)
.cornerRadius(highlightCorners)
}
}
// this just makes the syntax for using the modifier simpler.
extension View {
func highlighted(color: Color, highlightWidth: Float, highlightHeight: Float, highlightCorners: Float) -> some View {
modifier(Highlighted(color: color, highlightWidth: highlightWidth, highlightHeight: highlightHeight, highlightCorners: highlightCorners))
}
}
Then use it in the parent view as follows
let colors = [.yellow, .cyan, .red, .white]
...
ForEach(colors, id: \.self) { color in {
Rectangle()
.highlighted(color: color, highlightWidth: highlightWidth, highlightHeight: highlightHeight, highlightCorners: highlightCorners)
}

SwiftUI: Do I need to remove UIHostingController from the controller chain when I remove the controlled view from the view hierarchy?

I am trying to migrate UIKit Views in my app to SwiftUI. One of the central elements in my app is a UICollectionView. I am embedding the SwiftUI views using a UIHostingController - so far so good.
I am wondering, since my cells are reusable, what happens to the UIHostingController when the cell is recycled?
Do I need to take it out of the controller chain?
If I need to, what is the best way to do so? (storing the UIHostingController in the cell?)
Eg. a header view looks like this so far:
class HeaderViewCell: UICollectionReusableView {
var layoutAttributes:GroupHeaderViewLayoutAttributes = GroupHeaderViewLayoutAttributes()
public func attachContent(model:GroupHeaderModel, controller:UIViewController){
let view = GroupHeaderView(model: model, layoutAttributes: self.layoutAttributes)
let hostingController = UIHostingController(rootView: view)
if let contentView = hostingController.view {
controller.addChild(hostingController)
self.addSubviewAndConstrains(contentView)
}
}
override func prepareForReuse() {
self.subviews.forEach { $0.removeFromSuperview() }
layoutAttributes = GroupHeaderViewLayoutAttributes()
}
override func apply(_ layoutAttributes: UICollectionViewLayoutAttributes) {
if let attributes = layoutAttributes as? TileViewLayout.HeaderViewAttributes {
self.layoutAttributes.topBarHeight = attributes.topBarHeight
self.layoutAttributes.indicatorWidth = attributes.indicatorWidth
}
}
}

Xcode 11 Beta 4 TabbedView Binding Problem

Environment: Version 11.0 beta 4 (11M374r)
I'm trying to get a grip on the evolving List() syntax.
The following is a simple list of UUIDs:
via the pre-Beta 4 Code:
Here's my attempted remedy and the compiler result.
I've created an additional error after fixing the previous warning:
I tried replacing '#State' with '#Binding'; which didn't work.
Here's the complete source code:
import SwiftUI
enum TabIdentifier {
case list
case another
}
struct TabView: View {
private var uuids: [String] = {
let ids: [String] = Array(0...5).map { _ in
UUID().uuidString
}
return ids
}()
#State private var selectedTab: TabIdentifier = .list
var body: some View {
TabbedView(selection: $selectedTab) {
// ------------------------------------------------------------
// Tab #1
NavigationView {
List(uuids, id: \.id) { uuid in
Text(uuid)
}.navigationBarTitle(Text("List of UUIDs"))
}.tabItem {
Text("List") // ...Tab #1 Label
}
.tag(TabIdentifier.list) // ...Tab #1 tag
// ------------------------------------------------------------
// Tab #2
Text("Hello Ric!")
.tabItem {
Text("Another view") // ...Tab #2 Label
}
.tag(TabIdentifier.another) // ... Tab #2 Label
}
}
}
What's the remedy?
This is a misleading error. I'm pretty sure your issue is just a typo. It should be List(uuids, id: \.self), not List(uuids, id: \.id). Based on your code, uuids is just an array of strings, and String doesn't have a property id.

There doesn't appear to be support for UITabBar in SwiftUI. Workarounds?

SwiftUI doesn't appear to support UITabBar. How can I integrate that capability?
Merely wrapping the view like one would a (eg) MKMapView, doesn't work because of its need for deep integration with NavigationView. Using UINavigationView is too un-SwiftUI-ish.
The 'TabbedView' is the closest thing. It can be used similar to the following:
struct TabView : View {
#State private var selection = 1
var body: some View {
TabbedView (selection: $selection) {
InboxList()
.tabItemLabel(selection == 1 ? Image("second") : Image("first"))
.tag(1)
PostsList()
.tabItemLabel(Image("first"))
.tag(2)
Spacer()
.tabItemLabel(Image("first"))
.tag(3)
Spacer()
.tabItemLabel(Image("second"))
.tag(4)
}
}
}
If you aren't happy with TabbedView, you can always roll your own! Here's a quick base implementation:
import SwiftUI
struct ContentView : View {
let tabs = [TabItemView(title: "Home", content: { Text("Home page text") }), TabItemView(title: "Other", content: { Text("Other page text") }), TabItemView(title: "Pictures", content: { Text("Pictures page text") })]
var body: some View {
TabBar(tabs: tabs, selectedTab: tabs[0])
}
}
struct TabItemView<Content> : Identifiable where Content : View {
var id = UUID()
var title: String
var content: Content
init(title: String, content: () -> Content) {
self.title = title
self.content = content()
}
var body: _View { content }
typealias Body = Never
}
struct TabBar<Content>: View where Content : View {
let tabButtonHeight: Length = 60
var tabs: [TabItemView<Content>]
#State var selectedTab: TabItemView<Content>
var body: some View {
GeometryReader { geometry in
VStack(spacing: 0) {
self.selectedTab.content.frame(width: geometry.size.width, height: geometry.size.height - self.tabButtonHeight)
Divider()
HStack(spacing: 0) {
ForEach(self.tabs) { tab in
Button(action: { self.selectedTab = tab}) {
Text(tab.title)
}.frame(width: geometry.size.width / CGFloat(Double(self.tabs.count)), height: self.tabButtonHeight)
}
}
.background(Color.gray.opacity(0.4))
}
.frame(width: geometry.size.width, height: geometry.size.height)
}
}
}
UITabBar seems to be working now on Xcode 13.3, SwiftUI 3, iOS15+
It works even though I didn't import UIKit, not sure if that has any effect but it's working for me
struct LandingView: View {
#Binding var selectedTab: String
//hiding tab bar
init(selectedTab: Binding<String>) {
self._selectedTab = selectedTab
UITabBar.appearance().isHidden = true
}
var body: some View {
//Tab view with tabs
TabView(selection: $selectedTab) {
//Views
Home()
.tag("Home")
PlaylistView()
.tag("My Playlists")
HistoryView()
.tag("History")
}
}
}
I misstated the question as I was trying to make ToolBar... below is the code I ended up with... thanks to all.
struct ToolBarItem : Identifiable {
var id = UUID()
var title : String
var imageName : String
var action: () -> Void
}
struct TooledView<Content> : View where Content : View{
var content : Content
var items : [ToolBarItem]
let divider = Color.black.opacity(0.2)
init(items : [ToolBarItem], content: () -> Content){
self.items = items
self.content = content()
}
var body : some View{
VStack(spacing: 0){
self.content
self.divider.frame(height: 1)
ToolBar(items: self.items).frame(height: ToolBar.Height)
}
}
}
struct ToolBar : View{
static let Height : Length = 60
var items : [ToolBarItem]
var body: some View {
GeometryReader { geometry in
HStack(spacing: 0){
ForEach(self.items){ item in
Button(action: item.action){
Image(systemName: item.imageName).imageScale(.large)
Text(item.title).font(.caption)
}.frame(width: geometry.size.width / CGFloat(Double(self.items.count)))
}
}
.frame(height: ToolBar.Height)
.background(Color.gray.opacity(0.10))
}
}
}

Perfect JSON structure

I've been looking around and learning JSON a little bit. I thought it would be good to start learning with something easy but it seems it is not. I am trying to do JSON database. For example it has brand names and every brand has its own products with some info. I've done that like this which is actually much longer:
{
"Snuses": {
"Brands": {
"CATCH": [
{
"Products": "CATCH EUCALYPTUS WHITE LARGE",
"nicotine": "8.0"
}
]
}
Now I am using Firebase to parse the "Brands" like "CATCH" etc.. But I can't.
In swift I am trying to do it like this:
override func viewDidLoad() {
super.viewDidLoad()
ref = FIRDatabase.database().reference()
ref.observeSingleEventOfType(.Value, withBlock: { snapshot in
self.ref = FIRDatabase.database().reference().child("Snuses").child("Brands")
self.ref.observeEventType(.Value, withBlock: { snapshot -> Void in
for brands in snapshot.children {
print(brands)
}
})
})
}
How to get reference to the Brands first? And how to store list of brands separately?
Some smart guys told me that it is not correct to do but I don't know what is wrong with the JSON structure. How can I flatten it?
I red the docs also that says how it is best to do it but it is a little to complicaetd. Can you point me to the right direction?
You just need to do allKeys to get allKeys from snap
let ref = FIRDatabase.database().reference().child("Snuses").child("Brands")
ref.observeSingleEventOfType(.Value, withBlock: { (snapshot) in
if snapshot.exists() {
if let allProducts = (snapshot.value?.allKeys)! as? [String]{
self.snusBrandsArray = allProducts
self.productstable.reloadData()
}
}
})