SwiftUI Tutorial: Scrumdigger Spacer() issue - swift

I have been working through the SwiftUI Scrumdigger tutorial (https://developer.apple.com/tutorials/app-dev-training/getting-started-with-scrumdinger) and have reached Step 7 of "Creating a Card View". Here is where my experience deviates from what is presented by the tutorial.
Here is the code both I and the tutorial are using:
import SwiftUI
struct CardView: View {
let scrum: DailyScrum
var body: some View {
VStack(alignment: .leading) {
Text(scrum.title)
.font(.headline)
Spacer()
HStack {
Label("\(scrum.attendees.count)", systemImage: "person.3")
}
}
}
}
struct CardView_Previews: PreviewProvider {
static var scrum = DailyScrum.sampleData[0]
static var previews: some View {
CardView(scrum: scrum)
.background(scrum.theme.mainColor)
.previewLayout(.fixed(width: 400, height: 60))
}
}
Here is what I see:
And here is what the tutorial displays:
I have tried moving the Spacer() from line 9 to line 13 but the issue is the same.
Can reproduce without the reliance on the external Scrum data using this:
import SwiftUI
struct CardView: View {
//let scrum: DailyScrum
var body: some View {
VStack(alignment: .leading) {
Text("Title")
.font(.headline)
Spacer()
HStack {
Label("3", systemImage: "person.3")
}
}
}
}
struct CardView_Previews: PreviewProvider {
//static var scrum = DailyScrum.sampleData[0]
static var previews: some View {
CardView()
.previewLayout(.fixed(width: 400, height: 60))
}
}
Can anyone advise as to what might be going wrong here?

Related

Having trouble with showing another view

I'm currently trying to input another listview in my contentView file to test if it'll show, but for some reason it isn't showing the list. I'm having a bit of trouble understanding why this is happening as I am not receiving any error message.
This is the code for the list file
import SwiftUI
extension Image{
func anotherImgModifier() -> some View{
self
.resizable()
.scaledToFill()
.frame( width: 75, height: 75)
.cornerRadius(9)
}
}
struct PokeListView: View {
#State var imgURL: String = ""
#EnvironmentObject var pokeWebService: PokeWebService
//functions
// func loadImage() async -> [Image]{
// for
// }
var body: some View {
NavigationView {
List( pokeWebService.pokeList?.results ?? [], id: \.id){ pokemon in
NavigationLink(destination: PokeDetailsView(urlString: pokemon.url, counter: 4, name: pokemon.name)) {
AsyncImage(url:URL(string: "https://play.pokemonshowdown.com/sprites/bw/\(pokemon.name).png")){ image in
image.anotherImgModifier()
}
placeholder: {
Image(systemName: "photo.circle.fill").iconModifer()
}.padding(40)
Text(pokemon.name.uppercased()).font(.system(size: 15, weight: .heavy, design: .rounded))
.foregroundColor(.gray)
.task{
do{
try await pokeWebService.getPokemonFromPokemonList(from: pokemon.url)
} catch{
print("---> task error: \(error)")
}
}
}
}
}
.task {
do{
try await pokeWebService.getPokemonList()
} catch{
print("---> task error: \(error)")
}
}
}
}
struct PokeListView_Previews: PreviewProvider {
static var previews: some View {
PokeListView()
.previewLayout(.sizeThatFits)
.padding()
.environmentObject(PokeWebService())
}
}
This is the code for the ContentView where I was trying to input the list file.
import SwiftUI
struct ContentView: View {
#StateObject var newsWebService = NewsWebService()
#StateObject var pokeWebService = PokeWebService()
let gbImg = Image("pokeball").resizable()
#State private var gridLayout: [GridItem] = [ GridItem(.flexible()), GridItem(.flexible())]
#State private var gridColumn: Int = 2
#State var selection: Int? = nil
var body: some View {
NavigationView{
ScrollView(.vertical, showsIndicators: false, content: {
VStack(alignment: .center, spacing: 15, content: {
Spacer()
NewsCapsule()
//GRID
//BERRIES, POKEMON, GAMES
GroupBox(label: Label{
Text("PokéStuff")
} icon: {
Image("pokeball").resizable().scaledToFit().frame(width: 30, height: 30, alignment: .leading)
}
, content: {
PokeListView()
}).padding(.horizontal, 20).foregroundColor(.red)
})//:VSTACK
})//:SCROLLVIEW
.navigationBarTitle("Pokemon",displayMode: .large)
.toolbar(content: {
ToolbarItem(placement: .navigationBarTrailing, content: {
Image(systemName: "moon.circle")
.resizable()
.scaledToFit()
.font(.title2)
.foregroundColor(.red)
})
})
}//:NAVIGATIONBAR
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
.environmentObject(NewsWebService())
.environmentObject(PokeWebService())
}
}
How would I get to fix this?
EDIT-1:
with further tests, this is what worked for me:
in ContentView, add .frame(height: 666) to the VStack {...}.
This is the reason why you do not see anything. You need a frame height.
Also in ContentView, add .environmentObject(pokeWebService) to the NavigationView,
and just use PokeListView(). This is to pass the pokeWebService
to that view. After that, all works for me. You may want to experiment
with different frame sizes and such likes. You should also remove the NavigationView from your PokeListView, there is no need for it.

Problem to see Live Preview Cannot find a View struct in scope SwiftUI

When I want to load or call a view into another view, I have an error, but I can run simulator and see the app works fine; it just doesn't let me see Live Preview in Xcode.
The error I have:
Cannot find 'HomeTemplate' in scope
Sample code:
struct HomeController: View {
var body: some View {
HStack {
HomeTemplate()
}
}
}
struct HomeController_Previews: PreviewProvider {
static var previews: some View {
HomeController()
}
}
And HomeTemplate file:
import SwiftUI
struct HomeTemplate: View {
var body: some View {
ScrollView {
VStack {
Text("Your Daily")
.font(.system(size: 25, weight: .bold))
.foregroundColor(Color.secondary)
.frame(maxWidth: .infinity, alignment: .leading)
Text("Recommendation")
.font(.system(size: 25, weight: .bold))
.foregroundColor(Color.primary)
.frame(maxWidth: .infinity, alignment: .leading)
}
.padding(.horizontal, 22)
.padding(.top, 30)
ScrollViewListSlideStyleOne()
}
}
}
struct HomeTemplateView_Previews: PreviewProvider {
static var previews: some View {
HomeTemplate()
}
}
Why doesn't it let me call a struct or class wherever I want? I saw the other similar questions, but they just import SwiftUI, and it works for them, but I added it before.
By the way, I uploaded my project on GitHub and you can access to it:
https://github.com/shahryarjb/MishkaCmsiOS

How to call a view with a specific size?

I have a question about views in swiftui.
I have created a file CircleView which shows me a circle and a text in the middle. Additionally I have created a list in ContentView and a DetailView for details.
I now use the created circle in the list and on the detail view. Unfortunately the size of the circle is defined in the CircleView and I don't know how to change it individually so that it is displayed small in the list and uses the full width on the detail view.
So how can I change the size of a view individually?
Here is my code
CircleView:
struct CircleView: View {
var body: some View {
ZStack{
Circle()
.fill(Color.blue)
.frame(width: 250, height: 250)
Text("VIEW")
.frame(width: 100, height: 100)
.font(.largeTitle)
.foregroundColor(.white)
}
}
}
ContentView:
struct ContentView: View {
var body: some View {
NavigationView {
List{
NavigationLink(destination: DetailsView()) {
HStack{
Text("Hello")
Spacer()
CircleView()
}
}.navigationTitle("Home")
}
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
DetailsView:
struct DetailsView: View {
var body: some View {
VStack{
Text("Hello, World!")
.padding(10)
CircleView()
}
}
}
You should remove the set frame from your view and apply it when using your view.
Your code edited:
struct Sample: View {
var body: some View {
NavigationView {
List{
NavigationLink(destination: DetailsView()) {
HStack{
Text("Hello")
Spacer()
CircleView()
.frame(width: 100 , height: 100) // <-- Update frame here
}
}.navigationTitle("Home")
}
}
}
}
struct CircleView: View {
var body: some View {
ZStack{
Circle()
.fill(Color.blue)
Text("VIEW")
.font(.largeTitle)
.foregroundColor(.white)
}
}
}
struct Sample_Previews: PreviewProvider {
static var previews: some View {
Sample()
}
}
struct DetailsView: View {
var body: some View {
VStack{
Text("Hello, World!")
.padding(10)
CircleView().padding()
}
}
}

Multiline text truncated in ScrollView

I'm having a rather peculiar issue with multiline texts inside a ScrollView. I have a LazyVStack in the ScrollView and have SomeRow (see below) Views inside this stack.
I have a long multiline description inside this SomeRow View that SwiftUI always cuts off.
I have tried all of the solutions here but they either broke the layout, didn't work or straight up caused the app to crash.
How it looks at the moment:
I've tried to reduce the reproducing code down to a minimum, but it is unfortunately still quite long, because I want to preserve what look I am trying to accomplish.
Here is the SomeListView:
import SwiftUI
struct SomeListView: View {
var body: some View {
VStack{
Text("Title")
.font(.title)
ScrollView{
LazyVStack{
ForEach(0..<4){_ in
SomeRow(entries: EntryContainer(entries:
[
Entry("DESC\nLONG DESCRIPTION WITH OVERFLOW"),
Entry("")
]
))
Spacer().frame(height: 5)
Divider()
}
}
}.padding(16)
}
}
}
struct SomeListView_Previews: PreviewProvider {
static var previews: some View {
SomeListView()
}
}
SomeRow View
import SwiftUI
struct SomeRow: View {
var entries: [Entry]
var body: some View {
VStack(alignment: .leading, spacing: 0){
Text("title").frame(maxWidth: .infinity, alignment: .leading)
Spacer().frame(height: 5)
ForEach(entries){entry in
HStack{
VStack{
Text("00:00 - 00:00")
Spacer()
}.frame(minWidth: 110)
Divider()
VStack{
HStack{
Text("titleinner")
Spacer()
}
HStack{
Text(entry.description ?? "")
Spacer()
VStack{
Spacer()
Text("number")
}
}
Spacer()
}
Spacer()
}
}
}
}
}
struct SomeRow_Previews: PreviewProvider {
static var previews: some View {
SomeRow(entries:
[
Entry("DESC\nLONG DESCRIPTION WITH OVERFLOW"),
Entry("")
]
)
}
}
Entry Data Class
import Foundation
class Entry: Identifiable {
let description: String?
init(_ description: String? = nil) {
self.description = description
}
}
Any ideas for a workaround? I am assuming this is simply SwiftUI bug because if you set the same long description for both entries it magically shows both descriptions fully, which really doesn't make sense to me.
This is how it breaks when I use Text(entry.description ?? "").fixedSize(horizontal: false, vertical: true:
(The divider doesn't fill the full height anymore and alignment of the timestamps in the left column is wrong)
It’s nearly impossible to have single divider and maintain the same alignment as you are looking for. The closest I could get you is shown in below code. Hope it satisfies requirement to some extent.
import SwiftUI
struct SomeListView: View {
var obj = [
Entry("DESC LONG DESCRIPTION WITH OVERFLOW"),
Entry("")
]
var body: some View {
VStack{
Text("Title")
.font(.title)
ScrollView{
LazyVStack(){
ForEach(0..<4){_ in
SomeRow(entries: obj)
Spacer().frame(height: 5)
Divider()
}
}
}.padding(16)
}
}
}
class Entry: Identifiable {
let description: String?
init(_ description: String? = nil) {
self.description = description
}
}
struct SomeRow: View {
var entries: [Entry]
var body: some View {
VStack(alignment: .leading,spacing:7){
Text("title")
ForEach(entries){entry in
HStack(alignment: .top,spacing:15){
Text("00:00 - 00:00")
Divider()
VStack(alignment:.leading, spacing:8){
Text("titleinner")
HStack(alignment:.lastTextBaseline, spacing:0){
Text(entry.description ?? "")
Spacer()
Text("number")
}
}
}.fixedSize(horizontal: false, vertical: true)
}
}
}
}
struct SomeRow_Previews: PreviewProvider {
static var previews: some View {
SomeRow(entries:
[
Entry("DESC\nLONG DESCRIPTION WITH OVERFLOW"),
Entry("")
]
)
}
}
struct SomeListView_Previews: PreviewProvider {
static var previews: some View {
SomeListView()
}
}
Output-:
With the help of concepts from the answer from Tushar Sharma I was able to create a version that displays the multiline text and has a continuous divider between the two "columns".
The only changes are in SomeRow View (compared to my initial question):
struct SomeRow: View {
var entries: [Entry]
var body: some View {
VStack(alignment: .leading, spacing: 0){
Text("title").frame(maxWidth: .infinity, alignment: .leading)
Spacer().frame(height: 5)
ForEach(entries){entry in
HStack(alignment: .top){
VStack{
Text("00:00 - 00:00")
Spacer()
}.frame(minWidth: 110)
Divider()
VStack(alignment: .leading){
Text("titleinner")
HStack(alignment: .lastTextBaseline){
Text(entry.description ?? "")
Spacer()
Text("number")
}
Spacer()
}
Spacer()
}.fixedSize(horizontal: false, vertical: true)
}
}
}
}
Basically using specified alignments and using fixedSize on the whole container of the text works wonders.
This is how it looks like now:

SwiftUI: changing view using "if else"

I’m trying to change a view based on a selection made with a button. I’m using Combine and SwiftUI.
I create a view router class with a #Published var:
import Foundation
import SwiftUI
import Combine
class ViewRouter: ObservableObject {
#Published var currentView = "folder"
}
I have my button struct:
import SwiftUI
import Combine
struct MultiButton: View {
#ObservedObject var viewRouter = ViewRouter()
#State var showButton = false
var body: some View {
GeometryReader { geometry in
ZStack{
// open multi button
Button(action: {
withAnimation {
self.showButton.toggle()
}
}) {
Image(systemName: "gear")
.resizable()
.frame(width: 40, height: 40, alignment: .center)
}
if self.showButton {
Multi()
.offset(CGSize(width: -30, height: -100))
}
}
}
}
}
struct MultiButton_Previews: PreviewProvider {
static var previews: some View {
MultiButton()
}
}
struct Multi: View {
#ObservedObject var viewRouter = ViewRouter()
var body: some View {
HStack(spacing: 10) {
ZStack {
Button(action: {
self.viewRouter.currentView = "folder"
debugPrint("\(self.viewRouter.currentView)")
}) {
ZStack{
Circle()
.foregroundColor(Color.blue)
.frame(width: 70, height: 70)
Image(systemName: "folder")
.resizable()
.aspectRatio(contentMode: .fit)
.padding(20)
.frame(width: 70, height: 70)
.foregroundColor(.white)
}.shadow(radius: 10)
}
}
Button(action: {
self.viewRouter.currentView = "setting"
debugPrint("\(self.viewRouter.currentView)")
}, label: {
ZStack {
Circle()
.foregroundColor(Color.blue)
.frame(width: 70, height: 70)
Image(systemName: "gear")
.resizable()
.aspectRatio(contentMode: .fit)
.padding(20)
.frame(width: 70, height: 70)
.foregroundColor(.white)
}.shadow(radius: 10)
})
}
.transition(.scale)
}
}
And the contentView:
import SwiftUI
import Combine
struct ContentView: View {
#ObservedObject var viewRouter = ViewRouter()
var body: some View {
VStack{
MultiButton()
if viewRouter.currentView == "folder" {
Text("folder")
} else if viewRouter.currentView == "setting"{
Text("setting")
}
}
}
}
Why is my text is not changing from the folder to setting the base when the button is pressEd?
The var is #Published; it should publish the change and update the main view.
Did I miss something?
You have multiple instances of ViewRouter with each view having its own instance. If you change that instance, the other instances are not going to change.
To solve this, you either have to pass one instance around manually:
struct MultiButton {
#ObservedObject var viewRouter: ViewRouter
...
}
struct ContentView {
#ObservedObject var viewRouter = ViewRouter()
var body: some View {
VStack {
MultiButton(viewRouter: self.viewRouter)
...
}
}
}
Or you can declare it as an environment object:
struct MultiButton {
#EnvironmentObject var viewRouter: ViewRouter
...
}
And then inject it for example when you create your view hierarchy in your scene delegate:
let contentView = ContentView()
.environmentObject(ViewRouter())
Note that when injecting the environment object in the scene delegate, it will not be available in every preview of a view that uses the ViewRouter, unless you manually inject it there as well:
static var previews: some View {
ContentView()
.environmentObject(ViewRouter())
}