Shadows clipped by ScrollView - ios

Is there a way I can get shadows applied to an image to also show outside of the parent view? Below you can see a sample, where I list out some images in a ScrollView and HStack. The image/artwork has a shadow applied to it, but this gets clipped by the ScrollView.
Screenshot for reference:
ScrollView(.horizontal, showsIndicators: false) {
HStack {
ForEach(stationsFeed.items.sorted(by: { $0.updatedAt > $1.updatedAt }).prefix(15)) { item in
NavigationLink(destination: StationDetailView(station: item)) {
VStack(alignment: .leading) {
if item.artwork != nil {
KFImage(self.artworkURLForStation(item)!)
.resizable()
.frame(width: 130, height: 130)
.scaledToFit()
.cornerRadius(6)
.shadow(radius: 5)
} else {
RoundedRectangle(cornerRadius: 6)
.background(Color.purple)
.shadow(radius: 5)
}
Text(item.name)
.font(.callout)
.foregroundColor(.primary)
Text(item.categories.first?.name ?? "N/A")
.font(.callout)
.foregroundColor(.secondary)
}
.frame(minWidth: 0, maxWidth: 130, alignment: .leading)
}
.buttonStyle(PlainButtonStyle())
}
}
}

To make shadow uncut, add padding to HStack
ScrollView(.horizontal, showsIndicators: false) {
HStack {
ForEach(stationsFeed.items.sorted(by: { $0.updatedAt > $1.updatedAt }).prefix(15)) { item in
// ... other code
}.padding() // << here !!
}

I've found a decent workaround for this problem.
This is image BEFORE
This is image AFTER
I've added 2 paddings to ScrollView and to content of ScrollView with the same amount of padding but one with NEGATIVE value like this:
struct ScrollViewClippingProblem: View {
var body: some View {
VStack {
Text("Lorem Ipsum")
.font(.title)
ScrollView(.horizontal, showsIndicators: false) {
HStack(alignment: .top) {
ForEach(0..<10, content: { item in
Text("\(item)")
.font(.title)
.padding()
.background(Circle().fill(.red))
.shadow(color: .blue, radius: 10, x: 15, y: 10)
})
}
.padding(.vertical, 40) // <- add padding here to make the space for the shadow
}
.padding(.vertical, -40) // <- add NEGATIVE padding here of the same value to shrink the space you created with the padding above
Text("Lorem Ipsum")
.font(.title)
}
.background(.ultraThinMaterial)
}}
I hope this helps somebody! :)

There's a much simpler solution to this, provided that the shadows are uniform for each element within the HStack:
Just add the .shadow modifier to the ScrollView rather than to the element itself.
Using #Hollycene's answer for context.
See SwiftUI Preview Image
struct ScrollViewClippingSolutionView: View {
var body: some View {
VStack {
Divider().padding()
Text("Shadow on Element")
ScrollView(.horizontal, showsIndicators: false) {
HStack(alignment: .top) {
ForEach(0..<10) { item in
Text("\(item)")
.font(.title)
.padding()
.background(Circle().fill(.red))
.shadow(color: .blue, radius: 10, x: 15, y: 10) // <- Don't place .shadow modifier here
}
}
}
Divider().padding()
Text("Shadow on ScrollView")
ScrollView(.horizontal, showsIndicators: false) {
HStack(alignment: .top) {
ForEach(0..<10) { item in
Text("\(item)")
.font(.title)
.padding()
.background(Circle().fill(.red))
}
}
}
.shadow(color: .blue, radius: 10, x: 15, y: 10) // <- Place .shadow modifier here instead
Divider().padding()
}
}
}

Related

SwiftUI simple view, need a push into right direction

I am complete beginner with SwiftUI and I can't wrap my head around how to connect these images with views that represents lines. Now I simply have 3 VStacks with image and text and put them into a HStack, but don't know how to connect these images with a line shown in red in the picture I attached. Note that there's some space between the line and the image. I need general direction and some hints, full working code not necessary.
Thank you.
How's this?
In SwiftUI, you use HStacks and VStacks to stack your Views. For the red line, a Rectangle should do. Here's the code:
struct ContentView: View {
var body: some View {
HStack { /// horizontal stack
VStack {
Image(systemName: "face.smiling")
.font(.system(size: 80))
.padding()
.border(Color.black, width: 5)
Text("Text TEXTEXT")
}
Rectangle()
.fill(Color.red)
.frame(height: 5)
VStack {
Image(systemName: "face.smiling")
.font(.system(size: 80))
.padding()
.border(Color.black, width: 5)
Text("Text TEXTEXT")
}
Rectangle()
.fill(Color.red)
.frame(height: 5)
VStack {
Image(systemName: "face.smiling")
.font(.system(size: 80))
.padding()
.border(Color.black, width: 5)
Text("Text TEXTEXT")
}
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
.previewLayout(.fixed(width: 800, height: 200))
}
}
Version 1.0.0
I decided to give my answer which is same like aheze answer with this difference that you can have CustomVerticalAlignment as well! As I see in your Image in question you want that also:
with CustomVerticalAlignment: In center!
without CustomVerticalAlignment: off center!
import SwiftUI
struct ContentView: View {
var body: some View {
HStack(alignment: .customVerticalAlignment) {
VStack {
Image(systemName: "star")
.resizable()
.scaledToFit()
.frame(width: 50, height: 50, alignment: .center)
.padding()
.border(Color.black, width: 5)
.alignmentGuide(.customVerticalAlignment) { d in d[VerticalAlignment.center] }
Text("Text")
}
Capsule()
.fill(Color.red)
.frame(height: 5)
.alignmentGuide(.customVerticalAlignment) { d in d[VerticalAlignment.center] }
VStack {
Image(systemName: "star")
.resizable()
.scaledToFit()
.frame(width: 50, height: 50, alignment: .center)
.padding()
.border(Color.black, width: 5)
.alignmentGuide(.customVerticalAlignment) { d in d[VerticalAlignment.center] }
Text("Text")
}
Capsule()
.fill(Color.red)
.frame(height: 5)
.alignmentGuide(.customVerticalAlignment) { d in d[VerticalAlignment.center] }
VStack {
Image(systemName: "star")
.resizable()
.scaledToFit()
.frame(width: 50, height: 50, alignment: .center)
.padding()
.border(Color.black, width: 5)
.alignmentGuide(.customVerticalAlignment) { d in d[VerticalAlignment.center] }
Text("Text")
}
}
.padding()
}
}
extension VerticalAlignment {
struct CustomVerticalAlignment: AlignmentID {
static func defaultValue(in d: ViewDimensions) -> CGFloat {
d[VerticalAlignment.center]
}
}
static let customVerticalAlignment = VerticalAlignment(CustomVerticalAlignment.self)
}
Update Version 2.0.0
About this version: I would say it does the same job of version 1.0.0 in less code and also Text and Line are not depending on VStack or eachother any moere!
import SwiftUI
struct ContentView: View {
var body: some View {
HStack {
image.overlay(text.offset(y: 40), alignment: .bottom)
capsule
image.overlay(text.offset(y: 40), alignment: .bottom)
capsule
image.overlay(text.offset(y: 40), alignment: .bottom)
}
.padding(50)
}
var image: some View {
return Image(systemName: "star.fill")
.resizable()
.scaledToFit()
.padding(10)
.shadow(radius: 10)
.frame(width: 50, height: 50, alignment: .center)
.foregroundColor(Color.red)
.background(Color.yellow)
.border(Color.black, width: 5)
}
var capsule: some View {
return Capsule()
.fill(Color.red)
.frame(height: 5)
}
var text: some View {
return Text("Hello World!")
.lineLimit(1)
.fixedSize()
}
}
You could define a Shape that represents your line.
I used the spacing parameter of HStack to do the spacing:
struct MyLine : Shape {
func path(in rect: CGRect) -> Path {
Path { path in
path.move(to: CGPoint(x: 0, y: rect.midY))
path.addLine(to: CGPoint(x: rect.maxX, y: rect.midY))
}
}
}
struct ContentView: View {
var body: some View {
HStack(spacing: 10) {
VStack {
Image(systemName: "pencil")
Text("Label")
}
MyLine().stroke(Color.red)
VStack {
Image(systemName: "pencil")
Text("Label 2")
}
MyLine().stroke(Color.red)
VStack {
Image(systemName: "pencil")
Text("Label 3")
}
}
}
}
You could add a lineWidth parameter to make the stroke thicker:
.stroke(Color.red, lineWidth: 4)
Also, if you didn't using spacing on the HStack, you could using a padding modifier on either the VStacks or the MyLines to get the spacing.

KingFisher SwiftUI cancelOnDisappear method doesn't seem to prevent or cancel the call in a ScrollView

I have a swiftUI app. The app uses a scrollView LazyVGrid to present 3-photos wide by x rows. I am using the KingFisher Kingfisher package to download images. The performance of the app suffers as the cancelOnDisapper doesn't seem to be called. Thus, if the customer scrolls to the bottom of the 300+ photo list, the customer is required to wait until all 300+ photos are loaded to see the picture.
Whether I use .cancelOnDisappear(true) or not doesn't seem to make a difference.
It doesn't seem to matter where I put the .cancelOnDisappear(true) in the sequence of method calls.
I'm using the cancelOnDisappear method, but when I scroll down, every picture renders in the scrollView even if I scroll quickly. The onSuccess and onFailure methods don't seem to be called either as I am not seeing the debug statements from those calls.
Here is a snippit of the code:
var body: some View {
ZStack {
// specifics note set yet or calculating sale summary
if (!seriesList.isSpecificsListSet || isCalculatingSaleSummary) {
GeometryReader { geometry in
ProgressView("Loading...")
.scaleEffect(3, anchor: .center)
.progressViewStyle(CircularProgressViewStyle(tint: .red))
.frame(width: geometry.size.width,
height: geometry.size.height)
.zIndex(1)
}
.frame(minWidth: 0,
maxWidth: .infinity,
minHeight: 0,
maxHeight: .infinity,
alignment: .topLeading
).background(Color(.systemGray5))
.opacity(0.75)
.zIndex(1)
}
if isShowPhotos {
VStack {
ScrollView {
HStack(alignment: .center) {
VStack(alignment: .center) {
Text(kNewText)
.fontWeight(.semibold)
.frame(maxHeight: .infinity, alignment: .center)
Text(kLooseText)
.fontWeight(.semibold)
.frame(maxHeight: .infinity, alignment: .center)
}
VStack(alignment: .leading) {
HStack(spacing: 6) {
filterSwitch(specificsTypeTextEnum: .have, filterValue: $new_HaveShow)
filterSwitch(specificsTypeTextEnum: .want, filterValue: $new_WantShow)
filterSwitch(specificsTypeTextEnum: .sell, filterValue: $new_SellShow)
filterSwitch(specificsTypeTextEnum: .order, filterValue: $new_OrderShow)
Spacer()
}
HStack(spacing: 6) {
filterSwitch(specificsTypeTextEnum: .have, filterValue: $loose_HaveShow)
filterSwitch(specificsTypeTextEnum: .want, filterValue: $loose_WantShow)
filterSwitch(specificsTypeTextEnum: .sell, filterValue: $loose_SellShow)
Spacer()
}
}
} // hstack filter switches
SearchBar(searchText: $searchText, isSearching: $isSearching)
// beginning photos
LazyVGrid(columns: [
GridItem(.flexible(minimum: 100), spacing: 8, alignment: .top),
GridItem(.flexible(minimum: 100), spacing: 8, alignment: .top),
GridItem(.flexible(minimum: 100), spacing: 8)
], alignment: .leading, spacing: 9, content: {
switch showContextType {
case .series:
ForEach(showFigures
.filter({(
$0.seriesUniqueId == series.uniqueId && $0.searchString.lowercased().contains(searchText.lowercased()))
|| ($0.seriesUniqueId == series.uniqueId && searchText.isEmpty)}),
id: \.self)
{ figure in
FigurePhoto(figure: figure, needsRefresh: $needsRefresh)
}
case .whatIsNew:
ForEach(showFigures
.filter({
($0.addedDate > isNewAddedDate
&& $0.searchString.lowercased().contains(searchText.lowercased()))
|| ($0.addedDate > isNewAddedDate && searchText.isEmpty)})
.sorted(by: {$0.addedDate == $1.addedDate ? $0.figurePackageName < $1.figurePackageName : $0.addedDate > $1.addedDate}),
id: \.self)
{ figure in
FigurePhoto(figure: figure, needsRefresh: $needsRefresh)
}
case .allFigures:
ForEach(showFigures
.filter({
($0.searchString.lowercased().contains(searchText.lowercased()))
|| (searchText.isEmpty)})
.sorted(by: {$0.figurePackageName < $1.figurePackageName}),
id: \.self)
{ figure in
FigurePhoto(figure: figure, needsRefresh: $needsRefresh)
}
} // end showContent type switch
}) // end alignment & lazy grid
} // end list view
BannerVC().frame(width: 320, height: 50, alignment: .center)
} // end vstack
.navigationBarTitle(series.seriesName)
.navigationBarItems(
trailing: FigureListMenuItems(series: series,
showContextType: showContextType,
filterByPhase: $filterByPhase,
isShowPhotos: $isShowPhotos,
isCalculatingSaleSummary: $isCalculatingSaleSummary)
)
}
}
}
Code for the loaded photos
struct FigurePhoto: View {
#ObservedObject var figure: Figure
#Binding var needsRefresh: Bool
var body: some View {
NavigationLink(
destination: FigureDetailView(figure: figure, needsRefresh: $needsRefresh)) {
// name photo and specifics
VStack(alignment: .center, spacing: 4) {
// image and specifics
HStack(alignment: .top, spacing: 4) {
VStack(alignment: .center, spacing: 0) {
let image = figure.flickrPhotoString ?? ""
KFImage(URL(string: image))
.resizable()
.onSuccess { r in
#if DEBUG
print("success: \(r)")
#endif
}
.onFailure { error in
#if DEBUG
print("ERROR: Failure in KFImage: \(error.localizedDescription)")
#endif
}
.placeholder {
// Placeholder while downloading.
kMyToyBoxLogoImage
.resizable()
.opacity(0.3)
.scaledToFit()
.cornerRadius(22)
}
.cancelOnDisappear(true) // cancel if scrolled past
.scaledToFit()
.cornerRadius(22)
.overlay(
GeometryReader { geometry in
HStack {
Spacer()
VStack {
if figure.isSpecificsSet {
SpecificsImageOverlay(specificsType: .have,
newCount: figure.publishedSpecifics.new_haveCount,
looseCount: figure.publishedSpecifics.loose_haveCount)
SpecificsImageOverlay(specificsType: .want,
newCount: figure.publishedSpecifics.new_wantCount,
looseCount: figure.publishedSpecifics.loose_wantCount)
SpecificsImageOverlay(specificsType: .sell,
newCount: figure.publishedSpecifics.new_sellCount,
looseCount: figure.publishedSpecifics.loose_sellCount)
SpecificsImageOverlay(specificsType: .order,
newCount: figure.publishedSpecifics.new_orderCount,
looseCount: 0)
}
}
.frame(width: geometry.size.width/6)
}
}
, alignment: .bottom)
Text(figure.figurePackageName)
.font(.system(size: 10, weight: .semibold))
.lineLimit(1)
.padding(.top, 4)
// convert to strings to avoid commas
Text(figure.series.seriesName)
.font(.system(size: 9, weight: .regular))
.lineLimit(1)
Spacer()
} // end vstack
.onAppear() {
// primary front image use global unique id to avoid random matching numbers _small
figure.fetchFigureImageURL(withTags: figure.figureGlobalUniqueId, withText: "\(figure.figureGlobalUniqueId)\(kPrimaryFrontImageNameSuffix)\(kSmallSuffix)")
} // end vstack on appear
} // end hstack
} // end vstack
.overlay(
RoundedRectangle(cornerRadius: 16)
.stroke(Color.blue, lineWidth: 2)
)
} // end navigation link
} // end body
}
Based on the comment from #Asperi I moved the NavigationLink to an overlay. The performance is significantly improved and the .cancelOnDisappear(true) is functioning.
.overlay(
ZStack {
RoundedRectangle(cornerRadius: 16)
.stroke(Color.blue, lineWidth: 2)
NavigationLink(
destination: FigureDetailView(figure: figure, needsRefresh: $needsRefresh)) {
Rectangle()
.hidden()
}
}
)
Here is the full structure code for the PhotoView. I'm not sure if my implementation is ideal.
struct FigurePhoto: View {
#ObservedObject var figure: Figure
#Binding var needsRefresh: Bool
var body: some View {
// name photo and specifics
VStack(alignment: .center, spacing: 4) {
// image and specifics
HStack(alignment: .top, spacing: 4) {
VStack(alignment: .center, spacing: 0) {
let image = figure.flickrPhotoString ?? ""
KFImage(URL(string: image))
.resizable()
.onSuccess { r in
#if DEBUG
print("success: \(r)")
#endif
}
.onFailure { error in
#if DEBUG
print("ERROR: Failure in KFImage: \(error.localizedDescription)")
#endif
}
.placeholder {
// Placeholder while downloading.
kMyToyBoxLogoImage
.resizable()
.opacity(0.3)
.scaledToFit()
.cornerRadius(22)
}
.cancelOnDisappear(true) // cancel if scrolled past
.scaledToFit()
.cornerRadius(22)
.overlay(
GeometryReader { geometry in
HStack {
Spacer()
VStack {
if figure.isSpecificsSet {
SpecificsImageOverlay(specificsType: .have,
newCount: figure.publishedSpecifics.new_haveCount,
looseCount: figure.publishedSpecifics.loose_haveCount)
SpecificsImageOverlay(specificsType: .want,
newCount: figure.publishedSpecifics.new_wantCount,
looseCount: figure.publishedSpecifics.loose_wantCount)
SpecificsImageOverlay(specificsType: .sell,
newCount: figure.publishedSpecifics.new_sellCount,
looseCount: figure.publishedSpecifics.loose_sellCount)
SpecificsImageOverlay(specificsType: .order,
newCount: figure.publishedSpecifics.new_orderCount,
looseCount: 0)
}
}
.frame(width: geometry.size.width/6)
}
}
, alignment: .bottom)
Text(figure.figurePackageName)
.font(.system(size: 10, weight: .semibold))
.lineLimit(1)
.padding(.top, 4)
// convert to strings to avoid commas
Text(figure.series.seriesName)
.font(.system(size: 9, weight: .regular))
.lineLimit(1)
Spacer()
} // end vstack
.onAppear() {
// primary front image use global unique id to avoid random matching numbers _small
figure.fetchFigureImageURL(withTags: figure.figureGlobalUniqueId, withText: "\(figure.figureGlobalUniqueId)\(kPrimaryFrontImageNameSuffix)\(kSmallSuffix)")
} // end vstack on appear
} // end hstack
} // end vstack
.overlay(
ZStack {
RoundedRectangle(cornerRadius: 16)
.stroke(Color.blue, lineWidth: 2)
NavigationLink(
destination: FigureDetailView(figure: figure, needsRefresh: $needsRefresh)) {
Rectangle()
.hidden()
}
}
)
} // end body
}

Injecting A Button to ScrollView

I'm developing a iOS app relies on SwiftUI.
I have a ZStack view and inside it, I call a another view along with a button.
ZStack(alignment: .bottomTrailing) {
ImageStepView(data: self.data[randomImageNum])
Button(action: { self.showFavorites = true }) {
HStack {
Image(systemName: "suit.heart.fill")
Text("FAVORITES")
}
.frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: 15)
.foregroundColor(.white)
.padding()
.background(Color.black.opacity(0.6))
.cornerRadius(5)
.padding(.horizontal, 20)
}
}
ImageStepView.swift
struct ImageStepView: View {
var data: ImageDataModel
var body: some View {
GeometryReader { geometry in
ScrollView(.vertical) {
VStack{
Image(data.image)
.resizable()
.border(Color.white, width: 5)
.overlay(
Rectangle().stroke(Color.white, lineWidth: 4))
.shadow(color: Color.gray, radius: 10, x: 10, y: 10)
.scaledToFit()
.frame(height: geometry.size.height-110)
} .padding()
VStack{
VStack(alignment: .leading) {
HStack{...}
HStack {...}
}
Spacer()
.frame(height: 50)
VStack(alignment: .leading){
VStack{
HStack {...}
HStack {...}
HStack {...}
}
}
}.padding()
}.background(Color("Color").ignoresSafeArea(.all))
.frame(width: geometry.size.width)
.frame(minHeight: geometry.size.height)
}
}
}
ImageStepView has a ScroolView, that's why Button not appears on the end of ScroolView, it appears on bottom of the screen.
What I want is to show The Button not on bottom of the screen but end of the ImageStepView.
You can make the ImageStepView accept a generic parameter - a view to be injected:
struct ImageStepView<Injected: View>: View {
var data: ImageDataModel
var injectedView: () -> Injected
var body: some View {
GeometryReader { geometry in
ScrollView(.vertical) {
// ScrollView contents
injectedView()
}
.background(Color("Color").ignoresSafeArea(.all))
.frame(width: geometry.size.width)
.frame(minHeight: geometry.size.height)
}
}
}
and pass the injected view to the ImageStepView:
ImageStepView(data: self.data[randomImageNum]) {
Button(action: { self.showFavorites = true }) { ... }
}
or
ImageStepView(data: self.data[randomImageNum]) { EmptyView() }

Center View horizontally in SwiftUI

How can I center horizontally a View (Image) in an HStack? I want a button to be left aligned and the image to be centered horizontally the view.
Currently I have this structure:
VStack {
HStack {
Button(action: {
print("Tapped")
}, label: {
Image("left-arrow")
.resizable()
.frame(width: 30, height: 30, alignment: .leading)
}).padding(.leading, 20)
Spacer()
Image("twitter-logo")
.resizable()
.frame(width: 30, height: 30, alignment: .center)
}
Spacer()
}
Which is giving me this:
But I want to achieve this:
You can embed two HStack's in a ZStack and place spacers accordingly for the horizontal spacing. Embed all that in a VStack with a Spacer() to have everything pushed up to the top.
struct ContentView : View {
var buttonSize: Length = 30
var body: some View {
VStack {
ZStack {
HStack {
Button(action: {
}, label: {
Image(systemName: "star")
.resizable()
.frame(width: CGFloat(30), height: CGFloat(30), alignment: .leading)
}).padding(.leading, CGFloat(20))
Spacer()
}
HStack {
Image(systemName: "star")
.resizable()
.frame(width: CGFloat(30), height: CGFloat(30), alignment: .center)
}
}
Spacer()
}
}
}
Note: In the second HStack, the image should automatically be center aligned, but if it isn't, you can place a Spacer() before and after the image.
Edit: Added the VStack and Spacer() to move everything to the top like the OP wanted.
Edit 2: Removed padding on image because it caused the image to be slightly offset from the center. Since it is in its own HStack and center-aligned, it does not need padding.
Edit 3: Thanks to #Chris Prince in the comments, I decided to make a simple NavigationBar-esque custom view that you can provide left, center, and right arguments to create the effect that the OP desired (where each set of views are aligned independently of each other):
struct CustomNavBar<Left, Center, Right>: View where Left: View, Center: View, Right: View {
let left: () -> Left
let center: () -> Center
let right: () -> Right
init(#ViewBuilder left: #escaping () -> Left, #ViewBuilder center: #escaping () -> Center, #ViewBuilder right: #escaping () -> Right) {
self.left = left
self.center = center
self.right = right
}
var body: some View {
ZStack {
HStack {
left()
Spacer()
}
center()
HStack {
Spacer()
right()
}
}
}
}
Usage:
struct ContentView: View {
let buttonSize: CGFloat = 30
var body: some View {
VStack {
CustomNavBar(left: {
Button(action: {
print("Tapped")
}, label: {
Image(systemName: "star")
.resizable()
.frame(width: self.buttonSize, height: self.buttonSize, alignment: .leading)
}).padding()
}, center: {
Image(systemName: "star")
.resizable()
.frame(width: 30, height: 30, alignment: .center)
}, right: {
HStack {
Text("Long text here")
Image(systemName: "star")
.resizable()
.frame(width: 30, height: 30, alignment: .center)
.padding(.trailing)
}.foregroundColor(.red)
})
Spacer()
Text("Normal Content")
Spacer()
}
}
}
What's about saving button size to a property and add a negative padding to the image? And pay attention to an additional spacer after the image.
struct ContentView: View {
var buttonSize: Length = 30
var body: some View {
VStack {
HStack {
Button(action: {
print("Tapped")
}, label: {
Image(systemName: "star")
.resizable()
.frame(width: buttonSize, height: buttonSize, alignment: .leading)
})
Spacer()
Image(systemName: "star")
.resizable()
.frame(width: 30, height: 30, alignment: .center)
.padding(.leading, -buttonSize)
Spacer()
}
Spacer()
}
}
}
The result:
Easiest way for me:
ZStack(){
HStack{
Image("star").resizable().foregroundColor(.white).frame(width: 50, height: 50)
Spacer()
}
Image("star").resizable().font(.title).foregroundColor(.white).frame(width: 50, height: 50)
}
You center the view using position property try this code
Group{ // container View
Image("twitter-logo")
.resizable()
.frame(width: 30, height: 30, alignment: .center)
}.position(x: UIScreen.main.bounds.width/2)
the right way to center the Title like navigationbar:
HStack {
Spacer()
.overlay {
HStack {
Image(systemName: "star")
Spacer()
}
}
Text("Title")
Spacer()
.overlay {
HStack {
Spacer()
Image(systemName: "star")
}
}
}
You can place the view that you want to center into a VStack and then set the alignment to center. Make sure that you also set the frame(maxWidth: .infinity) or else it will be centering your view in the VStack but the VStack might not take up the entire width of the screen so you might not get the appearance you are trying to achieve.
To make it even easier, write it as a function that extends the View object
extension View {
func centerInParentView() -> some View {
VStack(alignment: .center) {
self
}
.frame(maxWidth: .infinity)
}
}
And then you can just call it as you would a view modifier i.e.
VStack {
HStack {
Button(action: {
print("Tapped")
}, label: {
Image("left-arrow")
.resizable()
.frame(width: 30, height: 30, alignment: .leading)
}).padding(.leading, 20)
Spacer()
Image("twitter-logo")
.resizable()
.frame(width: 30, height: 30, alignment: .center)
}
Spacer()
}
.centerInParentView()
Works every time for me
I have got an alternative solution. I used a hidden Image as placeholder.
HStack {
Image("left-arrow").padding()
Spacer()
Image("twitter-logo")
Spacer()
// placeholder to keep layout symmetric
Image("left-arrow").padding().hidden()
}
Of course you can replace the Images with Buttons or other Views as you prefer.
Here is what worked for me
HStack {
Image(systemName: "star.fill")
.frame(maxWidth: .infinity, alignment: .leading)
Image(systemName: "star.fill")
.frame(maxWidth: .infinity, alignment: .center)
Text("")
.frame(maxWidth: .infinity, alignment: .trailing)
}
.foregroundColor(.yellow)
Inspired by SwiftUI - How to align elements in left, center, and right within HStack?
Let me propose a different solution:
https://gist.github.com/buscarini/122516641cd0ee275dd367786ff2a736
It can be used like this:
HStack {
Color.red
.frame(width: 0, height: 50)
.layoutPriority(1)
GlobalHCenteringView {
Text("Hello, world!")
.lineLimit(1)
.background(Color.green)
}
.background(Color.yellow)
Color.red
.frame(width: 180, height: 50)
.layoutPriority(1)
}
}
This will center the child view in the screen if it fits, or leave it as is if it doesn't. It is currently using UIScreen, so it only works on iOS, but you could easily pass the screen or parent width to the constructor of the view, getting it from a GeometryReader or whatever.

ScrollView content not filling in SwiftUI

I have a scrollview which content is a VStack containing a ForEach loop to create some Rows instead of a list. A List has some downsides like the dividers.
My Issue is that the Row is not filling the scrollview. I think the scrollview width is not filling the screen.
NavigationView {
Toggle(isOn: $onlineStatus) {
Text("Online Only")
}.padding([.leading, .trailing], 15)
ScrollView {
VStack(alignment: .trailing) {
ForEach(onlineStatus ? self.notes.filter { $0.dot == .green } : self.notes) { note in
NavigationButton(destination: Text("LOL")) {
CardRow(note: note)
.foregroundColor(.primary)
.cornerRadius(8)
.shadow(color: .gray, radius: 3, x: 0, y: -0.01)
}.padding([.leading, .trailing, .top], 5)
}.animation(self.onlineStatus ? .fluidSpring() : nil)
}
}.padding([.leading, .trailing])
.navigationBarTitle(Text("Your documents"))
}
This is giving me this result:
That's my CardRow:
struct CardRow: View {
var note: Note
var body: some View {
HStack {
Image(uiImage: UIImage(named: "writing.png")!)
.padding(.leading, 10)
VStack(alignment: .leading) {
Group {
Text(note.message)
.font(.headline)
Text(note.date)
.font(.subheadline)
}
.foregroundColor(.black)
}
Spacer()
VStack(alignment: .trailing) {
Circle().foregroundColor(note.dot)
.frame(width: 7, height: 7)
.shadow(radius: 1)
.padding(.trailing, 5)
Spacer()
}.padding(.top, 5)
}
.frame(height: 60)
.background(Color(red: 237/255, green: 239/255, blue: 241/255))
}
}
Use .frame(minWidth: 0, maxWidth: .infinity) on the RowView or inside of it
Best solution I found is to use GeometryReader to fill the ScrollView's contents to the outer view's width. And be sure to use a Spacer() in each row's HStack. This handles safe areas and rotation well.
struct Card : View {
var body: some View {
HStack {
VStack(alignment: .leading) {
Text("Header")
.font(.headline)
Text("Description")
.font(.body)
}
Spacer()
}
.padding()
.background(Color.white)
.cornerRadius(8)
.shadow(color: Color.black.opacity(0.15), radius: 8, x: 0, y: 0)
}
}
struct Home : View {
var body: some View {
GeometryReader { geometry in
NavigationView {
ScrollView {
VStack(alignment: .leading, spacing: 16) {
Card()
Card()
Card()
}
.padding()
.frame(width: geometry.size.width)
}
.navigationBarTitle(Text("Home"))
}
}
}
}
See resulting screenshot:
Try setting the frame width of the RowView to UIScreen.main.bounds.width:
.frame(width: UIScreen.main.bounds.width)

Resources