SwiftUI - Image offset preview issue - swift

I have a navigation view which is a root view. After user login I would like to show Welcome View where I want to show user's image and user name.
Currently preview is a bit different to my device screen:
You may noticed that there is a Login back button on the simulator screen. Probably that's why I have the differences here.
Here is my code:
struct WelcomeView: View {
#EnvironmentObject var userService: UserService
var body: some View {
VStack {
Image("MountainWelcomBackground").resizable().frame(height: 300).edgesIgnoringSafeArea(.top)
CircleImage(image: userService.user.image!)
.offset(y: -220)
.frame(height: 140)
.frame(width: 140)
VStack(alignment: .leading) {
HStack(alignment: .top) {
Spacer()
Text(userService.user.username)
.font(.headline)
Spacer()
}
}
.offset(y: -200)
Spacer()
}
}
}
struct WelcomeView_Previews: PreviewProvider {
static var previews: some View {
let userService = UserService()
userService.user = User(username: "Alex Matrosov", email: "test.developer#gmail.com", avatar: nil, image: Image("ManPlaceholderAvatar"))
return WelcomeView().environmentObject(userService)
}
}
struct CircleImage: View {
var image: Image
var body: some View {
image
.resizable()
.clipShape(Circle())
.overlay(Circle().stroke(Color.white, lineWidth: 4))
.shadow(radius: 10)
.aspectRatio(contentMode: .fit)
}
}
How to make preview the same as on simulator? Probably I need to add Navigation view to preview somehow or maybe I set offset in a wrong way. Actually I just followed apple swiftui tutorial and they do this ui like me.

Offset depends on parent container, which differs in your case for real app running in Simulator and Preview showing only Welcome view.
Here is PreviewProvided to have same
struct WelcomeView_Previews: PreviewProvider {
static var previews: some View {
let userService = UserService()
userService.user = User(username: "Alex Matrosov", email: "test.developer#gmail.com", avatar: nil, image: Image("icon"))
return NavigationView {
WelcomeView().environmentObject(userService)
}
}
}

Related

SwiftUI - Creating a Star Rating View with decimal point fillable foreground colour in

I am trying to develop a star rating view using SwiftUI. I have a maximum of 5 stars, and the tricky part is that I need to fill 4.7 stars. So the last star should only fill up 70% of its foreground color.
Below is the code that I have done so far
import SwiftUI
struct AverageStarsView: View {
#Binding var rating: Double
var body: some View {
Group {
StarView()
}
.frame(maxHeight: 16)
}
}
struct AverageStarsView_Previews: PreviewProvider {
static var previews: some View {
AverageStarsView(rating: .constant(4.7))
}
}
struct StarView: View {
private var fillColor = .yellow
private var emptyColor = .grey
var body: some View {
ZStack {
if let starImage = UIImage(named: "icon-star", in: .sharedResources, compatibleWith: nil) {
Image(uiImage: starImage)
.resizable()
.renderingMode(.template)
.foregroundColor(emptyColor)
.aspectRatio(contentMode: .fit)
}
}
}
}
struct StarView_Previews: PreviewProvider {
static var previews: some View {
StarView()
}
}
How can fill only 70% of the star view.
I can't use .overlay because I need to support iOS 14.
Sample view
You can use the .mask() modifier that will create a mask of your view with a given view. Here is what you can achieve for a single star:
struct StarView: View {
var fillValue: Double
var body: some View {
GeometryReader { geometry in
ZStack(alignment: .leading) {
Rectangle()
Rectangle()
.fill(Color.orange)
.frame(width: geometry.size.width * fillValue)
}
}
.mask(
Image(systemName: "star.fill")
.resizable()
)
.aspectRatio(1, contentMode: .fit)
}
}
Here is the result with the fillValue set to 0.7:
You can then stack them into an HStack and make a quick calculation to know the fill value of the last star to make your five stars rating component.
Make sure to use .mask() and not .mask { } as the last one require iOS 15.0 to be used.

SwiftUI Tutorial: Scrumdigger Spacer() issue

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?

SwiftUI .overlay() causing data to be displayed incorrectly

I'm working on an app that calls an api and displays the data on 6 different cards. In the card view the data displays correctly. Once I place that view into another using .overlay() the UI is perfect but the data on each card is the same. Without .overlay() the data is correct but the UI is wrong. I've tried changing my ViewModel variable in the views to #EnvironmentObject, #StateObject, & #ObservedObject with no luck. I can't figure out if this is a bug or I am missing something. Any help is greatly appreciated.
This view displays data correctly:
import SwiftUI
struct TopActivityCardView: View {
// #EnvironmentObject var vm: ViewModel
#StateObject var vm = ViewModel()
var timePeriod = ""
var body: some View {
ZStack {
ScrollView{
ForEach(vm.fetch(), id: \.title) { item in
RoundedRectangle(cornerRadius: 20)
.foregroundColor(Color.darkBlue)
.frame(height: 120, alignment: .center)
.padding(.top, 80)
.overlay(
ActivityTitleView(activityTitle: item.title)
).overlay(
ActivityHoursView(workHours: item.timeframes.weekly.current)
).overlay(
ActivityEllipisView()
).overlay(
TimePeriodView(timePeriod: "Last Week", weeklyHrs:
item.timeframes.weekly.previous)
)
}
}
}
}
}
struct TopActivityCardView_Previews: PreviewProvider {
static var previews: some View {
TopActivityCardView()
.environmentObject(ViewModel())
}
}
This view is where the problem occurs:
import SwiftUI
struct RectCardView: View {
var colorArray: [(colorName: Color, imageName: String)] = [(Color.work, "icon-work"),
(Color.play, "icon-play"), (Color.study, "icon-study"), (Color.exercise, "icon-
exercise"), (Color.social, "icon-social"), (Color.selfCare, "icon-self-care")]
// #StateObject var vm = ViewModel()
#ObservedObject var vm = ViewModel()
var body: some View {
ZStack{
ScrollView{
ForEach(colorArray, id: \.imageName) { colorName, imageName in
RoundedRectangle(cornerRadius: 20)
.foregroundColor(colorName)
.frame(height: 110, alignment: .center)
.overlay(
Image(imageName)
.brightness(-0.2)
.frame(width: 60, height: 20, alignment: .topLeading)
.offset(x: 103, y: -60)
).clipped()
/// If I take .overlay() off the cards display correct data but UI is
not correct.
/// With overlay UI is correct but data on cards is all the same.
.overlay{
TopActivityCardView()
.padding(.top, -40)
}
.padding(.top, 20)
}
}
}
}
}
struct RectCardView_Previews: PreviewProvider {
static var previews: some View {`enter code here`
RectCardView()
.environmentObject(ViewModel())
}
}
In TopActivityCardView, you are making a new view model (#StateObject var vm = ViewModel()).
It isn't using the same view model as RectCardView, so the data is different.
You could pass the view into TopActivityCardView by using an environment object:
// code…
struct TopActivityCardView: View {
#EnvironmentObject var vm: ViewModel
// more code…
}
.overlay{
TopActivityCardView()
.environmentObject(vm)
.padding(.top, -40)
}

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

SwiftUI -> Thread 1: Fatal error: No ObservableObject of type ModelData found

I created an app with SwiftUI and when I try to display a button this error message appears:
Thread 1: Fatal error: No ObservableObject of type ModelData found. A View.environmentObject(_:) for ModelData may be missing as an ancestor of this view.
This occurs when I try to use an #EnvironmentObject when trying to display one of the views of my app.
My code is
struct OpportunityDetail: View {
#EnvironmentObject var modelData: ModelData
var opportunity: Opportunity
var opportunityIndex: Int {
modelData.opportunities.firstIndex(where: { $0.id == opportunity.id })!
}
var body: some View {
ScrollView {
MapView(coordinate: opportunity.locationCoordinate)
.frame(height: 300)
.ignoresSafeArea(edges: .top)
CircleImage(opportunity: opportunity)
.offset(y: -130)
.padding(.bottom, -130)
VStack {
VStack(alignment: .leading) {
Text(opportunity.position)
.font(.title)
HStack {
Text(opportunity.name)
.font(.subheadline)
Spacer()
Text(opportunity.city)
.font(.subheadline)
}
.font(.subheadline)
.foregroundColor(.secondary)
Divider()
Text("About the Opportunity")
.font(.title2)
Text(opportunity.description)
ApplyButton(isSet: $modelData.opportunities[opportunityIndex].isApplied)
}
.padding()
}
}
.navigationTitle(opportunity.name)
.navigationBarTitleDisplayMode(.inline)
}
}
However, the preview and live preview of the View work fine
struct OpportunityDetail_Previews: PreviewProvider {
static let modelData = ModelData()
static var previews: some View {
OpportunityDetail(opportunity: modelData.opportunities[0])
.environmentObject(modelData)
}
}
I know I have to pass my environment object somewhere but don't know how to. I would greatly appreciate anyone's help.
My issue was solved by putting .environmentObject(modelData) into ContentView as it was the root view of all of my subviews as New Dev said:
You have to use .environmentObject(modelData), just like you did for a preview, somewhere in the view hierarchy. For example, the parent view of OpportunityDetail could do it, or you can create it at the root level. For example, if ContentView is your root view, you can set it there.