How would I create the visual equivalent of a UIView in SwiftUI? - ios

Here is my code for my List items in my project using SwiftUI.
struct ServicesListItem: View {
var title: String
var description: String
var colorHex: String
var imageName: String
var body: some View {
HStack {
Image(imageName)
.resizable()
.aspectRatio(contentMode: .fit)
.frame(width: 50, height: 50)
VStack(alignment: .leading, spacing: 4) {
Text(title)
.font(.system(size: 14, weight: .black, design: .rounded))
.foregroundColor(Color(hex: colorHex))
Text(description)
.font(.system(size: 10, weight: .medium, design: .rounded))
.foregroundColor(Color.gray)
}
.frame(height: 60)
}
}
}
I want to put all of the elements of each ServicesListItem inside another "view" and add padding on all sides to give the appearance that the contents of each item are inside a raised view rather than the edge-to-edge look of the List. I've done this easily with UIKit by using a UIView as a parent view inside of a UITableViewCell. haven't found a means of doing this yet with SwiftUI. Is it possible?

SwiftUI 1.0
Hi Austin, here are a couple of relevant changes I made to your list item view:
Moved the frame to the HStack level
Made HStack fill width of device with .maxWidth: .infinity
Added a solid background color that you can then add a shadow to for the "raised look"
Code
struct ServicesListItem: View {
var title: String
var description: String
var colorHex: String
var imageName: String
var body: some View {
HStack {
Image(imageName)
.resizable()
.aspectRatio(contentMode: .fit)
.frame(width: 50, height: 50)
VStack(alignment: .leading, spacing: 4) {
Text(title)
.font(.system(size: 14, weight: .black, design: .rounded))
.foregroundColor(Color(hex: colorHex))
Text(description)
.font(.system(size: 10, weight: .medium, design: .rounded))
.foregroundColor(Color.gray)
}
}
// Set the size of entire row. maxWidth makes it take up whole width of device.
.frame(maxWidth: .infinity, maxHeight: 60, alignment: .leading)
.padding(10) // Spacing around all the contents
// Add a solid colored background that you can put a shadow on
// (corner radius optional)
.background(Color.white.cornerRadius(5).shadow(radius: 3))
}
}
Example
Is this similar to what you were thinking?
(As of SwiftUI 1.0, we don't know how to remove the lines. ☹️)

Related

Why is are the images being cut off?

The images in my 'tiles' are being cut off at the sides. I am trying to create a tile for each 'product' that displays an image, name and subtitle. Everything now works as it should besides the image. This is because the images on the tiles are being cut off at the sides.
The view for the ContentView is as follows:
ScrollView(.vertical, showsIndicators: false, content: {
Spacer()
LazyVGrid(columns: Array(repeating: GridItem(.flexible(),spacing: 15), count: 2),spacing: 10){
ForEach(HomeModel.filteredProduct){product in
// Product View...
ProductView(productData: product)
.background(.white)
.clipShape(RoundedRectangle(cornerRadius: 15, style: .continuous))
.shadow(color: .black.opacity(0.4), radius: 3, x: 1, y: 1)
.onTapGesture {
withAnimation(.easeIn){
selectedProduct = charity
show.toggle()
}
}
}
}
Spacer()
.padding()
.padding(.top,10)
})
.padding(.top, 20)
And the ProductView code is as follows:
var body: some View {
VStack(alignment: .center, spacing: 0) {
WebImage(url: URL(string: productData.product_image))
.resizable()
.aspectRatio(contentMode: .fill)
.frame(width: 160, height: 225)
.cornerRadius(15)
.clipped()
.padding(5)
Text(productData.product_name)
.font(.title2)
.fontWeight(.bold)
.foregroundColor(.black)
.padding()
Text(productData.product_type)
.font(.caption)
.foregroundColor(.gray)
.lineLimit(2)
.padding()
}
}
Here is an image showing what is meant by 'the tile is cutting it off':
As you can see in the photo, the image is not all included because it doesn't fit in the tile. This needs to be resized.
The first issue here would be the .contentMode modifier it needs to be set to fill. If you have an image where the width is larger then the height but you set the frame at a different aspect ratio you will see a white space above and below the image.
But there were other problems too. You need to clip an image with the .clipped() modifier after you set its frame, else it will overflow the frame. Why all those ZStacks? I cannot see any purpose of adding a ZStack with only one child.
BUT:
As your code is not reproducible i had to add or remove several things to make it work. So you need to adopt this to your code.
Solution:
struct ContentView: View{
#State private var products: [ProductData] = [ProductData(product_name: "test", product_details: "test"), ProductData(product_name: "test2", product_details: "test2"), ProductData(product_name: "test3", product_details: "test3")]
#State private var show = false
#State private var selectedProduct: ProductData?
var body: some View{
ScrollView(.vertical, showsIndicators: false, content: {
Spacer()
LazyVGrid(columns: Array(repeating: GridItem(.flexible(),spacing: 15), count: 2),spacing: 10){
ForEach(products){product in
// Product View...
ProductView(productData: product)
.background(.white)
.clipShape(RoundedRectangle(cornerRadius: 15, style: .continuous))
.shadow(color: .black.opacity(0.4), radius: 3, x: 1, y: 1)
.onTapGesture {
withAnimation(.easeIn){
selectedProduct = product
show.toggle()
}
}
}
}
Spacer()
.padding()
.padding(.top,10)
})
.padding(.top, 20)
}
}
struct ProductData: Identifiable{
var id = UUID()
var product_name: String
var product_details: String
}
struct ProductView: View{
var productData: ProductData
var body: some View{
VStack(alignment: .center, spacing: 0) {
Image("test")
.resizable()
.aspectRatio(contentMode: .fill)
.frame(width: 160, height: 225)
.cornerRadius(15)
.clipped()
.padding(5)
Text(productData.product_name)
.font(.title2)
.fontWeight(.bold)
.foregroundColor(.black)
.padding()
Text(productData.product_details)
.font(.caption)
.foregroundColor(.gray)
.lineLimit(2)
.padding()
}
}
}
Outcome:

SwiftUI - How to align elements in left, center, and right within HStack?

I'm creating an iOS minesweeper game and want to have a bar at the top with three pieces of information:
The high score (next to a trophy symbol) [LEFT]
The current time (next to a timer symbol) [CENTER]
The number of bombs left (next to a bomb symbol) [RIGHT]
However, the elements are not evenly aligned as you can see in the picture at the bottom- I'm guessing the spacers are automatically the same size. So, because the text on the left side of the screen is wider than that on the right, the middle text is offset to the right.
How can I align these items so the center time/image is perfectly in the middle with the other objects on the left/right?
My code looks like:
struct GameView: View {
#EnvironmentObject var game: Game
#State var labelHeight = CGFloat.zero
var body: some View {
VStack(alignment: .center, spacing: 10) {
Text("MINESWEEPER")
.font(.system(size: 25, weight: .bold, design: .monospaced))
.kerning(3)
.foregroundColor(.red)
HStack(alignment: .center, spacing: 5) {
Image(systemName: "rosette")
.resizable()
.aspectRatio(contentMode: .fit)
.foregroundColor(.black)
Text(game.highScoreString)
.font(.system(size: 15, weight: .bold, design: .monospaced))
.foregroundColor(.black)
Spacer()
Image(systemName: "timer")
.resizable()
.aspectRatio(contentMode: .fit)
.foregroundColor(.black)
Text(game.timerString)
.font(.system(size: 15, weight: .bold, design: .monospaced))
.foregroundColor(.black)
Spacer()
Image("top_bomb")
.resizable()
.aspectRatio(contentMode: .fit)
Text(String(game.bombsRemaining))
.font(.system(size: 15, weight: .bold, design: .monospaced))
.foregroundColor(.black)
.overlay(GeometryReader(content: { geometry in
Color.clear
.onAppear(perform: {self.labelHeight = geometry.frame(in: .local).size.height})
}))
}
.frame(width: UIScreen.main.bounds.width * 0.9, height: labelHeight, alignment: .center)
BoardView()
}
}
}
Instead of aligning by spacers, move each part into separate view, say LeftHeader, CenterHeader, and RightHeader and use frame alignment, which gives exact separation by equal sizes, like
HStack {
LeftHeader()
.frame(maxWidth: .infinity, alignment: .leading)
CenterHeader()
.frame(maxWidth: .infinity, alignment: .center)
RightHeader()
.frame(maxWidth: .infinity, alignment: .trailing)
}
This can be achieved using a ZStack , HStack and Spacers.
ZStack{
HStack{
Text("Left")
Spacer()
}
HStack{
Text("Center")
}
HStack{
Spacer()
Text("Right")
}
}
Even if you remove left ( or right ) HStack, it won't affect the other components or the layout.
You can make it look bit nicer by adding paddings like this,
ZStack{
HStack{
Text("Left").padding([.leading])
Spacer()
}
HStack{
Text("Center")
}
HStack{
Spacer()
Text("Right").padding([.trailing])
}
}
I think you can create more one stack.
My solution like:
/**
* spacing is an example
* minWidth calculated by fontSize
*/
HStack(alignment: .center, spacing: 20){
HStack{ Image, Text }.frame(minWidth: 30)
HStack{ Image, Text }.frame(minWidth: 30)
HStack{ Image, Text }.frame(minWidth: 30)
}
Hope it useful for you.

How do I make an Image in Swift the same height as the text next to it?

I'm brand new to Swift and am making an iOS Minesweeper app. At the top of the screen I want to have an image of a bomb next to a Text() object displaying the number of bombs remaining.
I'd like to have the image and/or HStack resize to whatever the height of the adjacent text is without having to hard-code it's dimensions.
Can/how this be achieved?
The code so far looks like this:
HStack(alignment: .center, spacing: 10) {
Image("top_bomb")
.resizable()
.aspectRatio(contentMode: .fit)
Text(String(game.bombsRemaining))
.font(.system(size: 30, weight: .bold, design: .monospaced))
}
.frame(width: UIScreen.main.bounds.width, height: UIScreen.main.bounds.height * 0.1, alignment: .trailing)
And it looks like:
You just need to fix it by size, like
HStack(alignment: .center, spacing: 10) {
Image("top_bomb")
.resizable()
.aspectRatio(contentMode: .fit)
Text(String(game.bombsRemaining))
.font(.system(size: 30, weight: .bold, design: .monospaced))
}
.fixedSize() // << here !!
.frame(maxWidth: .infinity, alignment: .trailing) // << !!
Note: also pay attention that you don't need to hardcode frame to screen size
#State var labelHeight = CGFloat.zero
HStack(alignment: .center, spacing: 10) {
Image("top_bomb")
.resizable()
.aspectRatio(contentMode: .fit)
.frame(maxHeight: labelHeight)
Text(String(game.bombsRemaining))
.font(.system(size: 30, weight: .bold, design: .monospaced))
.overlay(
GeometryReader(content: { geometry in
Color.clear
.onAppear(perform: {
self.labelHeight = geometry.frame(in: .local).size.height
})
})
)
}
.frame(width: UIScreen.main.bounds.width, height: UIScreen.main.bounds.height * 0.2, alignment: .trailing)

How to hide the overflow of the background colour when adding corner radius in SwiftUI?

I want to have a view with rounded corners and a custom background colour, but the latter overflows in the corners, as showed here:
This is my code:
var id:Int
var body: some View {
VStack(alignment: .leading) {
Text(name)
.font(.title2)
.bold()
.foregroundColor(.accentColor)
Text("Index numéro " + String(id))
.foregroundColor(.accentColor.opacity(0.75))
}
.padding()
.frame(width: UIScreen.width*0.9,
height: UIScreen.height*0.1,
alignment: .leading)
.overlay(RoundedRectangle(cornerRadius: 20)
.stroke(Color.accentColor, lineWidth: 3))
.background(Color.accentColor.opacity(0.1))
}
Thanks in advance, I hope you guys will help me solve this issue!
You can use a RoundedRectangle with strokeBorder and then a background of an additional RoundedRectangle with a fill modifier. This technique is detailed here: SwiftUI: How to draw filled and stroked shape?
var body: some View {
VStack(alignment: .leading) {
Text(name)
.font(.title2)
.bold()
.foregroundColor(.accentColor)
Text("Index numéro " + String(id))
.foregroundColor(.accentColor.opacity(0.75))
}
.frame(maxWidth: .infinity, alignment: .leading)
.padding()
.background(
RoundedRectangle(cornerRadius: 20)
.strokeBorder(Color.accentColor, lineWidth: 3)
.background(
RoundedRectangle(cornerRadius: 20).fill(Color.accentColor.opacity(0.3))
)
)
.padding()
}
Note: I've also changed your .frame modifier and replaced it with padding, which generally would be a more flexible solution that doesn't rely on measuring the screen size, but it's inconsequential to this answer
Simply replace this code:
.background(Color.accentColor.opacity(0.1).cornerRadius(20.0))
In iOS 15 there is a .background() modifier clipped to shape.
func background<S, T>(_ style: S, in shape: T, fillStyle: FillStyle = FillStyle()) -> some View where S : ShapeStyle, T : InsettableShape
Try this:
struct ContentView: View {
var body: some View {
VStack(alignment: .leading) {
Text("name")
.font(.title2)
.bold()
.foregroundColor(.accentColor)
Text("Index numéro " + "1")
.foregroundColor(.accentColor.opacity(0.75))
}
.padding()
.frame(width: 200,
height: 100,
alignment: .leading)
.overlay(RoundedRectangle(cornerRadius: 20)
.stroke(Color.accentColor, lineWidth: 3))
.background(Color.accentColor.opacity(0.1), in: RoundedRectangle(cornerRadius: 20))
}
}

How to create a 2 column grid with square shaped cells in SwiftUI

I'm trying to replicate this UI in SwiftUI using a Grid.
I created the cell like this.
struct MenuButton: View {
let title: String
let icon: Image
var body: some View {
Button(action: {
print(#function)
}) {
VStack {
icon
.resizable()
.aspectRatio(contentMode: .fit)
.frame(width: 60, height: 60)
Text(title)
.foregroundColor(.black)
.font(.system(size: 20, weight: .bold))
.multilineTextAlignment(.center)
.padding(.top, 10)
}
}
.frame(width: 160, height: 160)
.overlay(RoundedRectangle(cornerRadius: 10).stroke(Color.fr_primary, lineWidth: 0.6))
}
}
And the Grid like so.
struct LoginUserTypeView: View {
private let columns = [
GridItem(.flexible(), spacing: 20),
GridItem(.flexible(), spacing: 20)
]
var body: some View {
ScrollView {
LazyVGrid(columns: columns, spacing: 30) {
ForEach(Menu.UserType.allCases, id: \.self) { item in
MenuButton(title: item.description, icon: Image(item.icon))
}
}
.padding(.horizontal)
.padding()
}
}
}
But on smaller screens like the iPod, the cells are overlapped.
On bigger iPhone screens, still the spacing is not correct.
What adjustments do I have to make so that in every screen size, the cells would show in a proper square shape and equal spacing on all sides?
MenuButton has fixed width and height, thats why it behaves incorrectly.
You could utilise .aspectRatio and .frame(maxWidth: .infinity, maxHeight: .infinity) for this:
struct MenuButton: View {
let title: String
let icon: Image
var body: some View {
Button(action: {
print(#function)
}) {
VStack(spacing: 10) {
icon
.resizable()
.aspectRatio(contentMode: .fit)
.frame(maxWidth: 60, maxHeight: 60)
Text(title)
.foregroundColor(.black)
.font(.system(size: 20, weight: .bold))
.multilineTextAlignment(.center)
}
.padding()
}
.frame(maxWidth: .infinity, maxHeight: .infinity)
.aspectRatio(1, contentMode: .fill)
.overlay(RoundedRectangle(cornerRadius: 10).stroke(Color. fr_primary, lineWidth: 0.6))
}
}

Resources