Add background blur to part of image in SwiftUI - swiftui

I would like to create a card component in SwiftUI. The only element missing is the blurred background of the top bar. I have tried using the approach mentioned in this thread but for me it only resulted in a gray background bar.
The card I'd like to create.
The way it looks right now.
BlurView
struct VisualEffectView: UIViewRepresentable {
var effect: UIVisualEffect?
func makeUIView(context: UIViewRepresentableContext<Self>) -> UIVisualEffectView { UIVisualEffectView() }
func updateUIView(_ uiView: UIVisualEffectView, context: UIViewRepresentableContext<Self>) { uiView.effect = effect }
}
My View:
var body: some View {
ZStack {
Image("Vegetarische-Pizza")
.resizable()
.aspectRatio(contentMode: .fill)
.frame(height: 160.0)
.cornerRadius(8.0)
VStack {
ZStack {
VisualEffectView(effect: UIBlurEffect(style: .prominent))
HStack {
Image("Star")
.resizable()
.aspectRatio(contentMode: .fit)
.frame(height: 20.0)
Spacer()
Text("Vegetarische Pizza")
.font(FontManager.headline3)
.foregroundColor(ColorManager.meal)
Spacer()
Image("Leaf")
.resizable()
.aspectRatio(contentMode: .fit)
.frame(height: 20.0)
}
.padding(4.0)
}
.frame(height: 30.0)
Spacer()
ZStack {
Rectangle()
.foregroundColor(.clear)
.background(LinearGradient(gradient: .init(colors: [ColorManager.meal_gradient, .clear]), startPoint: .bottom, endPoint: .top))
.frame(height: 30)
.cornerRadius(8.0)
Text("Montag, 07.12.2020")
.font(FontManager.headline3)
.foregroundColor(ColorManager.meal)
}
}
}
.frame(height: 160.0)
}

Related

SwiftUI transition is not working when in HSack

I have a VStack which wraps a number elements wrapped inside another HStack, I want to add a simple bottom to top transition on load, but it does not have any effect on the output:
var body: some View {
let bottomPadding = ScreenRectHelper.shared.bottomPadding + 150
GeometryReader { geometry in
ZStack {
VisualEffectViewSUI(effect: UIBlurEffect(style: .regular))
.edgesIgnoringSafeArea(.all)
VStack(alignment: .center) {
Spacer()
HStack {
VStack(alignment: .leading, spacing: 15) {
ForEach(items, id: \.id) { item in
HStack(alignment: .center) {
Image(item.imageName)
.resizable()
.scaledToFit()
.frame(width: 24)
Spacer()
.frame(width: 15)
Text("\(item.text)")
.foregroundColor(.white)
.transition(.move(edge: .bottom))
}
.contentShape(Rectangle())
.onTapGesture {
/// Do something
}
}
}
}
.frame(width: geometry.size.width, height: nil, alignment: .center)
}.zIndex(1)
.frame(width: geometry.size.width, height: nil, alignment: .center)
.padding(.bottom, bottomPadding)
}.background(
LinearGradient(gradient: Gradient(colors: [gradientColor.opacity(0),gradientColor]), startPoint: .top, endPoint: .bottom)
)
}.edgesIgnoringSafeArea(.all)
}
This SwiftUI view is added to a UIKit view controller and that view controller is presented modally.
.transition only works if you insert something conditionally into the view.
I'm not totally sure what you want to achieve, but if you want the whole view to slide from the bottom, this would work.
struct ContentView: View {
#State private var showText = false
let bottomPadding: CGFloat = 150
var body: some View {
ZStack {
if showText { // conditional view to make .transition work
// VisualEffectViewSUI(effect: UIBlurEffect(style: .regular))
// .edgesIgnoringSafeArea(.all)
VStack(alignment: .center) {
Spacer()
HStack {
VStack(alignment: .leading, spacing: 15) {
ForEach(0 ..< 3) { item in
HStack(alignment: .center) {
Image(systemName: "\(item).circle")
.resizable()
.scaledToFit()
.frame(width: 24)
Spacer()
.frame(width: 15)
Text("Item \(item)")
.foregroundColor(.white)
}
}
}
}
}
.frame(maxWidth: .infinity)
.padding(.bottom, bottomPadding)
.background(
LinearGradient(gradient: Gradient(colors: [.blue.opacity(0), .blue]),
startPoint: .top, endPoint: .bottom)
)
.edgesIgnoringSafeArea(.all)
.transition(.move(edge: .bottom)) // here for whole modal view
}
Button("Show modal") {
withAnimation {
showText.toggle()
}
}
}
}
}

SwiftUI How to vertically center a view inside a VStack

I would like to have one view in the vertical center of the screen, one view at the top of the screen and one view vertically centered between these two views like so:
This took me 5min to do on a storyboard but I don't seem to find a way to do it in SwiftUI 🙃.
I already tried with multiple zstacks, vstacks, multiple custom alignments but this is the closest I got:
struct SelectionView: View {
var body: some View {
ZStack(alignment: .myAlignment) {
Color.green
.edgesIgnoringSafeArea(.all)
VStack {
Image(systemName: "clock")
.resizable()
.foregroundColor(.white)
.aspectRatio(contentMode: .fit)
.frame(width: 156, height: 80)
// Spacer()
Text("My\nmultiline label")
.multilineTextAlignment(.center)
.font(.title)
.foregroundColor(.white)
// Spacer()
VStack(spacing: 16) {
RoundedRectangle(cornerRadius: 5).fill(Color.white).frame(height: 79)
RoundedRectangle(cornerRadius: 5).fill(Color.white).frame(height: 79)
}
.alignmentGuide(VerticalAlignment.myAlignment) { dimension in
dimension[VerticalAlignment.center]
}
.layoutPriority(1)
}
.padding([.leading, .trailing], 24)
}
}
}
struct SelectionView_Previews: PreviewProvider {
static var previews: some View {
LanguageSelectionView()
}
}
// MARK
extension HorizontalAlignment {
enum MyHorizontal: AlignmentID {
static func defaultValue(in d: ViewDimensions) -> CGFloat
{ d[HorizontalAlignment.center] }
}
static let myAlignment =
HorizontalAlignment(MyHorizontal.self)
}
extension VerticalAlignment {
enum MyVertical: AlignmentID {
static func defaultValue(in d: ViewDimensions) -> CGFloat
{ d[VerticalAlignment.center] }
}
static let myAlignment = VerticalAlignment(MyVertical.self)
}
extension Alignment {
static let myAlignment = Alignment(horizontal: .myAlignment,
vertical: .myAlignment)
}
I'm keeping the GeometryReader as a last resort as it feels like a too drastic measure for this seemingly simple layout..
I guess I'm approaching this in some wrong way (still too much UIKit/Constraints in my head)..
Here is possible solution. Tested with Xcode 12 / iOS 14
struct SelectionView: View {
var body: some View {
ZStack {
Color.green
.edgesIgnoringSafeArea(.all)
VStack(spacing: 16) {
Color.clear
.overlay(
VStack {
Image(systemName: "clock")
.resizable()
.foregroundColor(.white)
.aspectRatio(contentMode: .fit)
.frame(width: 156, height: 80)
Color.clear
.overlay(
Text("My\nmultiline label")
.multilineTextAlignment(.center)
.font(.title)
.foregroundColor(.white)
)
}
)
RoundedRectangle(cornerRadius: 5).fill(Color.white).frame(height: 79)
RoundedRectangle(cornerRadius: 5).fill(Color.white).frame(height: 79)
Color.clear
}
.padding([.leading, .trailing], 24)
}
}
}

Is there a way to use Spacer() in a ZStack? (SwiftUI)

I have text on top of a rectangle through a ZStack and I was wondering if there was a way to limit the Spacer() amount within the rectangle.
ZStack{
Rectangle()
.frame(width: geometry.size.width,
height: geometry.size.height/3.25)
.shadow(radius: 5)
.foregroundColor(Color.white)
//Words ontop of the Rectangle
VStack {
HStack {
Spacer()
Text("Hello World")
}.padding(.trailing, 40)
Spacer() //<-- PROBLEM HERE
}//.offset(y: -40)
}
What It Looks Like
tl;dr:
I'd like to have is so that "Hello World", doesn't go passed the bounds of the rectangle when Spacer() is used. How can I do this? Thanks
I think what you're trying to accomplish can be achieved by just using a ZStack and specifying .topTrailing alignment:
struct ContentView: View {
var body: some View {
GeometryReader { geometry in
ZStack(alignment: .topTrailing) {
Rectangle()
.frame(height: geometry.size.height/3.25)
.shadow(radius: 5)
.foregroundColor(Color.white)
Text("Hello World")
.padding(.trailing, 40)
}
.frame(maxHeight: .infinity)
}
}
}
Alternatively:
You can forego the ZStack and make the Text an .overlay() of the Rectangle(). Here I kept your VStack and just made it an overlay which keeps it from going beyond the Rectangle.
struct ContentView: View {
var body: some View {
GeometryReader { geometry in
Rectangle()
.frame(height: geometry.size.height/3.25)
.shadow(radius: 5)
.foregroundColor(Color.white)
.overlay(
VStack {
HStack {
Spacer()
Text("Hello World")
}.padding(.trailing, 40)
Spacer()
}
)
.frame(maxHeight: .infinity)
}
}
}

full screen background image with vstack

i want to have a full screen background image with a navigationview (must be on top because it is from the basis view and not in "this" view normally).
In this view i want a VStack which is just inside the secure area, so between navigationbar and bottom layout.
unfortunately i got (see picture)
I expected the texts inside...
struct ContentView: View {
var body: some View {
NavigationView {
ZStack(alignment: .center) {
Image("laguna")
.resizable()
.edgesIgnoringSafeArea(.all)
.scaledToFill()
.frame(width: UIScreen.main.bounds.width, height: UIScreen.main.bounds.height)
VStack(alignment: .center) {
Text("just a test")
.font(.largeTitle)
.foregroundColor(Color.white)
Spacer()
Text ("not centered....why?")
.font(.largeTitle)
.foregroundColor(Color.white)
}
.zIndex(4)
.navigationBarTitle("nav bar title")
}
}
}
}
Here is a bit modified variant. Tested with Xcode 11.4 (finally released) / iOS 13.4
struct TestFullScreenImage: View {
var body: some View {
NavigationView {
ZStack {
Image("large_image")
.resizable()
.edgesIgnoringSafeArea(.all)
.scaledToFill()
VStack {
Text("Just a test")
.font(.largeTitle)
.foregroundColor(.white)
Spacer()
Text("centered")
.font(.largeTitle)
.background(Color.green)
}
.navigationBarTitle("Navigation Title")
}
}
.frame(maxWidth: .infinity, maxHeight: .infinity)
}
}
If need to use NavigationView, and maintain the image's aspect ratio, you can do this:
import SwiftUI
struct FullScreenPictureDemo: View {
var body: some View {
NavigationView {
ZStack {
Image("your_full_screen_background_picture")
.resizable()
.aspectRatio(contentMode: .fill)
.edgesIgnoringSafeArea(.all)
.scaledToFill()
}
}
.frame(maxWidth: .infinity, maxHeight: .infinity)
}
}

SwiftUI - Tapping a Button changes VStack's background color

When I tapped OrderButton, all the VStack's background color is changing to gray color then turning back like assigned a click event. How can I prevent this?
import SwiftUI
struct DrinkDetail : View {
var drink: Drink
var body: some View {
List {
ZStack (alignment: .bottom) {
Image(drink.imageName)
.resizable()
.aspectRatio(contentMode: .fit)
Rectangle()
.frame(height: 80)
.opacity(0.25)
.blur(radius: 10)
HStack {
VStack(alignment: .leading, spacing: 3) {
Text(drink.name)
.foregroundColor(.white)
.font(.largeTitle)
Text("It is just for $5.")
.foregroundColor(.white)
.font(.subheadline)
}
.padding(.leading)
.padding(.bottom)
Spacer()
}
}
.listRowInsets(EdgeInsets())
VStack(alignment: .leading) {
Text(drink.description)
.foregroundColor(.primary)
.font(.body)
.lineLimit(nil)
.lineSpacing(12)
HStack {
Spacer()
OrderButton()
Spacer()
}
.padding(.top, 50)
.padding(.bottom, 50)
}
.padding(.top)
}
.edgesIgnoringSafeArea(.top)
.navigationBarHidden(true)
}
}
struct OrderButton : View {
var body: some View {
Button(action: {}) {
Text("Order Now")
}
.frame(width: 200, height: 50)
.foregroundColor(.white)
.font(.headline)
.background(Color.blue)
.cornerRadius(10)
}
}
#if DEBUG
struct DrinkDetail_Previews : PreviewProvider {
static var previews: some View {
DrinkDetail(drink: LoadModule.sharedInstance.drinkData[3])
}
}
#endif
The issue resolved by using ScrollView and adding some more parameters for auto resize of Text:
ScrollView(.vertical, showsIndicators: false) {
ZStack (alignment: .bottom) {
Image(drink.imageName)
.resizable()
.aspectRatio(contentMode: .fit)
Rectangle()
.frame(height: 80)
.opacity(0.25)
.blur(radius: 10)
HStack {
VStack(alignment: .leading, spacing: 3) {
Text(drink.name)
.foregroundColor(.white)
.font(.largeTitle)
Text("It is just for $5.")
.foregroundColor(.white)
.font(.subheadline)
}
.padding(.leading)
.padding(.bottom)
Spacer()
}
}
.listRowInsets(EdgeInsets())
VStack(alignment: .leading) {
Text(drink.description)
.foregroundColor(.primary)
.font(.body)
.lineLimit(nil)
.fixedSize(horizontal: false, vertical: true)
.lineSpacing(12)
.padding(Edge.Set.leading, 15)
.padding(Edge.Set.trailing, 15)
HStack {
Spacer()
OrderButton()
Spacer()
}
.padding(.top, 50)
.padding(.bottom, 50)
}
.padding(.top)
}
.edgesIgnoringSafeArea(.top)
.navigationBarHidden(true)
What's the UI you are trying to get? Because, from your example, it seems that List is not necessary. Indeed, List is:
A container that presents rows of data arranged in a single column.
If you don't really need List you can replace it with VStack (if your content must fit the screen) or with a ScrollView (if your content is higher than the screen). Replacing the List will solve your issue that depends on the list selection style of its cells.
An alternative, if you need to use the List view is to set the selection style of the cells in the SceneDelegate to none (but this will affect all of your List in the project) this way:
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
let contentView = ContentView() //replace this with your view
if let windowScene = scene as? UIWindowScene {
UITableViewCell.appearance().selectionStyle = .none
let window = UIWindow(windowScene: windowScene)
window.rootViewController = UIHostingController(rootView: contentView)
self.window = window
window.makeKeyAndVisible()
}
}