Set SwiftUI view to always stay one size - swift

I have a view that I always want to remain the same size, I haven't made it responsive to different screen sizes as eventually it will convert to a PDF. How do I set a specific view to always remain a size, and when I show it in a sheet it will display in the same format, just scaled down?
This is the view I am trying to maintain the size and aspect ratio of:
ScrollView(.vertical){
VStack(spacing: 0){
ForEach(processor.matchEvents){event in
if(event.team.home == true){
HomeArm(team: event.team, event: event) //Same as AwayArm, just changes text
}
else{
AwayArm(team: event.team, event: event)
}
}
}
}
struct AwayArm: View {
#ObservedObject var team: Team
var event: MatchEvent
var body: some View {
let mins: String = String(event.time)
ZStack {
Rectangle()
.frame(width: 5, height: 200)
HStack(spacing: 0){
Spacer()
Text("\(mins)'")
.font(.title)
.frame(width: 60, height: 60, alignment: .center)
.foregroundColor(team.teamTextColor)
.background(team.teamColor)
.clipShape(Circle())
.modifier(RoundedEdge(width: 5, color: .black, cornerRadius: 40))
Rectangle()
.frame(width: 80, height: 5)
VStack{
Text(event.event)
.font(.title)
Text("Event Details")
.font(.title2)
Text("\(event.player.shirtNumber) - \(event.player.playerName)")
.font(.title3)
}.padding()
.foregroundColor(.white)
.frame(width: 250, height: 150)
.background(LinearGradient(gradient: Gradient(colors: [Color(UIColor.lightGray), .gray]), startPoint: .top, endPoint: .bottom))
.cornerRadius(25)
.modifier(RoundedEdge(width: 5, color: .black, cornerRadius: 30))
}
.padding(.top)
.padding(.trailing, 35)
}
}
}
I want this to be displayed inside a sheet, but maintain the correct sizings and aspect ratio:
HStack{
ScrollView(.vertical){
MatchReportView()
}
Text("")
}
Any help on keeping this the same size, all the time, would be appreciated

Related

Why does ScrollView randomly push my views content downwards?

Here is my code:
import SwiftUI
struct TrendingView : View {
#State var selectedTab: Tabs = .hot
#State private var searchText = ""
var body: some View {
NavigationView {
VStack {
HStack {
Text(" Trending")
.font(.largeTitle)
.frame(width: 175, height: 50, alignment: .leading)
.overlay(
RoundedRectangle(cornerRadius: 5)
.stroke(.black, lineWidth: 2)
)
.italic()
.fontWeight(.heavy)
.foregroundColor(Color(hue: 1.0, saturation: 0.977, brightness: 0.985))
.offset(x: -75, y: -25)
.padding(.bottom, -20)
NavigationLink(destination: testT()) {
Image(systemName: "magnifyingglass.circle.fill")
.resizable()
.scaledToFit()
.frame(width: 40, height: 40)
.foregroundColor(.black)
.padding(.bottom, -20)
}
.offset(x: 50, y: -25)
.padding(.leading, 5.0)
}
ScrollView {
VStack {
Text("Hot Items 🔥")
.bold()
.italic()
.offset(x: 5)
.frame(width: 120, height: 25, alignment: .leading)
.overlay(
RoundedRectangle(cornerRadius: 5)
.stroke(.black, lineWidth: 2)
)
.background(.yellow)
.offset(x: -130, y: 5)
ScrollView(.horizontal) {
HStack {
NavigationLink(destination: ImageTest()) {
Image("testpic1")
.resizable()
.frame(width: 200, height: 200)
}
NavigationLink(destination: ImageTest()) {
Image("testpic3")
.resizable()
.frame(width: 200, height: 200)
}
}.offset(y: 30)
}
}
}
There seems to be some error with scrollview above my vstack as it randomly causes all the content on this view to shift downwards when switching to and from other views.Ive tried removing the scrollview and the error disappears and also tried changing it around in other places but I still get the error Can anybody point me in the right direction? Thanks
Fixed, added to the bottom of my code and it seems like the issue is solved
.navigationBarTitle(Text(""), displayMode: .inline)
.navigationBarHidden(true)

SwiftUI - How to center a component in a horizontal ScrollView when tapped?

I have a horizontal ScrollView, and within it an HStack. It contains multiple Subviews, rendered by a ForEach. I want to make it so that when these Subviews are tapped, they become centered vertically in the view. For example, I have:
ScrollView(.horizontal) {
HStack(alignment: .center) {
Circle() // for demonstration purposes, let's say the subviews are circles
.frame(width: 50, height: 50)
Circle()
.frame(width: 50, height: 50)
Circle()
.frame(width: 50, height: 50)
}
.frame(width: UIScreen.main.bounds.size.width, alignment: .center)
}
I tried this code:
ScrollViewReader { scrollProxy in
ScrollView(.horizontal) {
HStack(alignment: .center) {
Circle()
.frame(width: 50, height: 50)
.id("someID3")
.frame(width: 50, height: 50)
.onTapGesture {
scrollProxy.scrollTo(item.id, anchor: .center)
}
Circle()
.frame(width: 50, height: 50)
.id("someID3")
.frame(width: 50, height: 50)
.onTapGesture {
scrollProxy.scrollTo(item.id, anchor: .center)
}
...
}
}
But it seemingly had no effect. Does anyone know how I can properly do this?
You can definitely do this with ScrollView and ScrollViewReader. However, I see a couple of things that could cause problems in your code sample:
You use the same id "someID3" twice.
I can't see where your item.id comes from, so I can't tell if it actually contains the same id ("someID3").
I don't know why you have two frames with the same bounds on the same view area. It shouldn't be a problem, but it's always best to keep things simple.
Here's a working example:
import SwiftUI
#main
struct MentalHealthLoggerApp: App {
var body: some Scene {
WindowGroup {
ScrollViewReader { scrollProxy in
ScrollView(.horizontal) {
HStack(alignment: .center, spacing: 10) {
Color.clear
.frame(width: (UIScreen.main.bounds.size.width - 70) / 2.0)
ForEach(Array(0..<10), id: \.self) { id in
ZStack(alignment: .center) {
Circle()
.foregroundColor(.primary.opacity(Double(id)/10.0))
Text("\(id)")
}
.frame(width: 50, height: 50)
.onTapGesture {
withAnimation {
scrollProxy.scrollTo(id, anchor: .center)
}
}
.id(id)
}
Color.clear
.frame(width: (UIScreen.main.bounds.size.width - 70) / 2.0)
}
}
}
}
}
}
Here you can see it in action:
[EDIT: You might have to click on it if the GIF won't play automatically.]
Note that I added some empty space to both ends of the ScrollView, so it's actually possible to center the first and last elements as ScrollViewProxy will never scroll beyond limits.
Created a custom ScrollingHStack and using geometry reader and a bit calculation, here is what we have:
struct ContentView: View {
var body: some View {
ScrollingHStack(space: 10, height: 50)
}
}
struct ScrollingHStack: View {
var space: CGFloat
var height: CGFloat
var colors: [Color] = [.blue, .green, .yellow]
#State var dragOffset = CGSize.zero
var body: some View {
GeometryReader { geometry in
HStack(spacing: space) {
ForEach(0..<15, id: \.self) { index in
Circle()
.fill(colors[index % 3])
.frame(width: height, height: height)
.overlay(Text("\(Int(dragOffset.width))"))
.onAppear {
dragOffset.width = geometry.size.width / 2 - ((height + space) / 2)
}
.onTapGesture {
let totalItems = height * CGFloat(index)
let totalspace = space * CGFloat(index)
withAnimation {
dragOffset.width = (geometry.size.width / 2) - (totalItems + totalspace) - ((height + space) / 2)
}
}
}
}
.offset(x: dragOffset.width)
.gesture(DragGesture()
.onChanged({ dragOffset = $0.translation})
)
}
}
}

How to put an Image on top of a Color View in SwiftUI

I have an image that needs to be put in the top center of my view. How can I do so?
struct MyTextField: View
{
#ObservedObject var model: TextFieldModel
var body: some View
{
VStack(alignment: .leading, spacing: -60)
{
Color.black.position(CGPoint(x: 200, y: -50)).frame(width: 417, height: 120,
alignment: .top)
.background(Image(("appIconLogo")).resizable().frame(width: 50, height: 50))
//Spacer(minLength: 5)
VStack(alignment: .leading, spacing: 30)
{
Text("Welcome!")
.font(.system(size: 60, design: .rounded))
.foregroundColor(.black)
iTextField("Search Your Ticker", text: $model.text, isEditing: $model.isEditing)
.foregroundColor(.black)
.showsClearButton(true)
.onReturn
{
print($model.text)
NotificationCenter.default.post(name: Notification.Name(rawValue:
"isValidTicker"), object: nil)
}
.foregroundColor(.black)
.style(height: 58, font: nil, paddingLeading: 25, cornerRadius: 6,
hasShadow: true, image: Image(systemName: "magnifyingglass"))
.foregroundColor(.black)
}
}
.position(CGPoint(x: 200.0, y: 150.0))
.padding()
}
}
But my View looks like this. How can I move my image to the top center of the page where the black color is?
Here is a demo of possible layout. Prepared with Xcode 12.1 / iOS 14.1.
struct MyTextField: View
{
var body: some View
{
VStack {
Color.black
.frame(height: 120)
.overlay(Image(("plant")).resizable().frame(width: 50, height: 50))
.edgesIgnoringSafeArea(.top)
VStack(alignment: .leading, spacing: 30)
{
Text("Welcome!")
.font(.system(size: 60, design: .rounded))
.foregroundColor(.black)
.padding(.leading)
// .. other code
}.frame(maxWidth: .infinity, alignment: .leading)
}.frame(maxHeight: .infinity, alignment: .top)
}
}
It's a little confusing what you are trying to do in your code, but I think this might get you started. I made a background layer that extends to the edge of the screen, and then put a Content layer on top of it, where you can put the Image at the top of the VStack.
You should try to avoid setting exact width/height/CGPoints whenever possible in SwiftUI.
var body: some View {
ZStack {
// Background
Color.white
.edgesIgnoringSafeArea(.all)
// Content
VStack(spacing: 20) {
Image(systemName: "heart.fill") // Image here
Text("Welcome!") // Title
.font(.system(size: 60, design: .rounded))
.frame(maxWidth: .infinity, alignment: .leading)
.foregroundColor(.black)
TextField("Search your ticker", text: $textFieldText) // Textfield
.frame(height: 50)
.background(Color.gray)
.cornerRadius(10)
Spacer()
}
.padding()
}
}

SwiftUI different children alignment and GeometryReader

I want to transform a web app into an iOS app and I am struggling with alignments and the GeometryReader().
This a a screenshot from my original web app template:
This is my current version in SwiftUI:
With the related code:
struct PileRow: View {
var body: some View {
HStack() {
VStack(alignment: .leading, spacing: 3) {
Text("Adobe".uppercased())
Bar(backgroundColor: Color.gray, width: 200)
Bar(backgroundColor: Color.black, width: 200)
HStack(alignment: .top, spacing: 3) {
Text("EUR 1,00")
.fontWeight(.light)
Text(" / ")
.fontWeight(.light)
.foregroundColor(Color.gray)
Text("35,69")
.fontWeight(.light)
.foregroundColor(Color.gray)
}
Image("dots")
.resizable()
.scaledToFit()
.frame(width: 20)
}
.font(.system(size: 14))
.padding()
}
.background(Color.white)
.cornerRadius(15)
.shadow(radius: 6, x: 5, y: 5)
}
}
struct Bar: View {
var backgroundColor: Color
var width: CGFloat
var body: some View {
Rectangle()
.fill(backgroundColor)
.frame(width: width, height: 3)
.cornerRadius(1.5)
}
}
I want:
the dots image to be centered in the parent VStack (like margin: 0 auto; in CSS)
the outer HStack to be 100% width with a left and right margin of 10px (like width: 100%; margin: auto 10px; in CSS)
The special challenge I want to realize is, to give the second Bar() view a percentage with (or some equivalent) to have a with of 1.00/35.69*100%.
In the apple documentation of Swift UI I found:
GeometryReader
A container view that defines its content as a function of its own size and coordinate space. 3
With a change from:
Bar(backgroundColor: Color.gray, width: 200)
Bar(backgroundColor: Color.black, width: 200)
to
GeometryReader { g in
Bar(backgroundColor: Color.gray, width: g.size.width)
}
.frame(height: 3)
GeometryReader { g in
Bar(backgroundColor: Color.black, width: g.size.width*(1/35.69))
}
.frame(height: 3)
it is going close to what I look for:
I don't understand what is happening here. My purpose was to give the Bar views a size relationship to the parent. But the parent itself gets a different width and hight that I need to fix with a frame with a height of 3 and the parent HStack fills the entire screen.
When I try this:
GeometryReader { g in
Bar(backgroundColor: Color.gray, width: g.size.width)
Bar(backgroundColor: Color.black, width: g.size.width*(1/35.69))
}
I am completely out, because the bars get somehow vstacked:
I think this is what you want:
import SwiftUI
struct ContentView: View {
var body: some View {
GeometryReader { g in
HStack {
VStack(alignment: .leading, spacing: 3) {
Text("Adobe".uppercased())
Bar(backgroundColor: Color.gray, width: g.size.width)
Bar(backgroundColor: Color.black, width: g.size.width*(1/35.69))
HStack(alignment: .top, spacing: 3) {
Text("EUR 1,00")
.fontWeight(.light)
Text(" / ")
.fontWeight(.light)
.foregroundColor(Color.gray)
Text("35,69")
.fontWeight(.light)
.foregroundColor(Color.gray)
}
HStack {
Spacer()
Image(systemName: "dots")
.resizable()
.scaledToFit()
.frame(width: 20)
Spacer()
}
}
.font(.system(size: 14))
.padding()
}
.background(Color.white)
.cornerRadius(15)
.shadow(radius: 6, x: 5, y: 5)
}
.padding()
}
}
struct Bar: View {
var backgroundColor: Color
var width: CGFloat
var body: some View {
Rectangle()
.fill(backgroundColor)
.frame(width: width, height: 3)
.cornerRadius(1.5)
}
}

Align rectangle to be within certain distance of safe area bottom edge SWIFTUI

I am used to Interface Builder and layout constraints but now I want to convert my app to Swift UI. What I am trying to do right now is align the top edge of the view marked with a 1 to be within a certain distance of the safe area bottom edge (marked with a 2) so that the top edge that is now at 1 will then be at position 3. I tried using spacers but then it will look different on smaller devices such as an iPhone 8. In IB I could have used a simple layout constraint. How does this work in Swift UI? I have attached the relevant code and an image. Thank you for your help.
struct ContentView: View {
init() {
UINavigationBar.appearance().backgroundColor = .orange
}
var body: some View {
NavigationView {
VStack{
Spacer()
ZStack{
Rectangle()
.fill(Color(hue: 0, saturation: 0, brightness: 0, opacity: 0.1))
Image("")
.resizable()
.aspectRatio(contentMode: .fit)
.frame(width: 100, height: 150)
.clipShape(Circle())
.overlay(Circle().stroke(Color.white, lineWidth: 4))
.shadow(radius: 10)
}.frame(width: UIScreen.main.bounds.width, height: UIScreen.main.bounds.width, alignment: .center)
Spacer(minLength: 100)
ZStack(alignment: .bottom){
ExtractedView()
.edgesIgnoringSafeArea(.bottom)
}
}
.navigationBarTitle(Text("You See"))
.navigationBarHidden(false)
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
.previewDevice("iPhone")
}
}
struct ExtractedView: View {
#State private var name: String = ""
var body: some View {
VStack{
ZStack(alignment: .top){
VStack{
RoundedRectangle(cornerRadius: 50)
.frame(width: 60, height: 7)
.padding(.top)
Button(action: /*#START_MENU_TOKEN#*/{}/*#END_MENU_TOKEN#*/) {
Text("Start advertising")
.font(.headline)
.foregroundColor(Color.black)
}.padding(.top)
TextField("Test", text: $name)
.padding(.all)
.background(Color.white.cornerRadius(20))
.padding()
}
RoundedRectangle(cornerRadius: 30)
.fill(Color(hue: 0, saturation: 0, brightness: 0, opacity: 0.1))
.zIndex(-5)
}
}
}
}
Ok, so I was able to solve my problem. The trick is to create two VStacks with different frame alignments. The outer VStack has top alignment so that the Discover view can be at the top. The inner VStack has a bottom alignment so that the sheet can be pulled up from the bottom. Space will be filled from the bottom up in this case.
VStack{
Discover()
.padding(.top, 60.0)
VStack{
Text("Recent Messages:")
.font(.headline)
DetailSheet()
.offset(x: 0, y: 0)
}.frame(width: g.size.width, height: (g.size.height - g.size.width - 80 + 200), alignment: .bottom)
}
.frame(width: g.size.width, height: g.size.height, alignment: .top)
you can try this:
offset -10 is just your offset you want to have....
i hope i understood you right, i am not so sure...
var body: some View {
VStack{
// ZStack(alignment: .top){
VStack{
RoundedRectangle(cornerRadius: 50)
.frame(width: 60, height: 7)
.padding(.top)
Button(action: /*#START_MENU_TOKEN#*/{}/*#END_MENU_TOKEN#*/) {
Text("Start advertising")
.font(.headline)
.foregroundColor(Color.black)
}.padding(.top)
TextField("Test", text: $name)
.padding(.all)
.background(Color.white.cornerRadius(20))
.padding()
}.background(RoundedRectangle(cornerRadius: 30)
.fill(Color(hue: 0, saturation: 0, brightness: 0, opacity: 0.1))
).offset(y:-10)
}
// }