i wrote a configurable 'View extension' for tvOS (but probably would work on ios as well), save these next two files:
import Foundation
import SwiftUI
extension View
{
/**
function that creates the background color that "shines" through the 'IceCube'
*/
func createColorBGLayer(at: CGRect,
background: Color,
cornerRadius: CGFloat) -> some View
{
return background
.clipShape(
RoundedRectangle(cornerRadius: cornerRadius)
.offset(x: at.minX + 5, y: at.minY + 5)
.size(CGSize(width: at.width - 10, height: at.height - 10))
)
}
/**
function that creates the 'IceCube' (a clipped image with blur)
*/
func createIceLayer(at: CGRect,
backgroundOpacity: CGFloat,
cornerRadius: CGFloat,
blurRadius: CGFloat) -> some View
{
return self
.opacity(backgroundOpacity)
.clipShape(
RoundedRectangle(cornerRadius: cornerRadius)
.offset(x: at.minX, y: at.minY)
.size(CGSize(width: at.width, height: at.height))
)
.blur(radius: blurRadius)
}
/**
function that creates the text layer in the center of the 'IceCube'
*/
func createTextLayer(at: CGRect,
textString: String,
fontSize: CGFloat,
fontDesign: Font.Design,
fontWeight: Font.Weight,
foregroundColor: Color) -> some View
{
// calculate render width and height of text using provided font (without actually rendering)
let sizeOfText: CGSize = textString.sizeUsingFont(fontSize: fontSize, weight: fontWeight)
let textOffsetX = at.minX + ((at.width - sizeOfText.width) / 2)
let textOffsetY = at.minY + ((at.height - sizeOfText.height) / 2)
// render text in center of iceCube
return GeometryReader { proxy in
Text(textString)
.font(Font.system(size: fontSize, design: fontDesign))
.fontWeight(fontWeight)
.foregroundColor(foregroundColor)
// put the text in the middle of the blured rectangle
.offset(x: textOffsetX, y: textOffsetY)
}
}
/**
main function to create the ice cube ontop of this extended view
*/
func iceCube(at: CGRect,
textString: String = "",
fontSize: CGFloat = 40,
fontWeight: Font.Weight = Font.Weight.regular,
fontDesign: Font.Design = Font.Design.rounded,
foregroundColor: Color = Color.white,
background: Color = Color.white,
backgroundOpacity: CGFloat = 0.9,
cornerRadius: CGFloat = 30,
blurRadius: CGFloat = 8) -> some View
{
// clipped image at the original position blurred and rounded corner
return self
.overlay(
ZStack {
// first layer color white for a beat of glare
createColorBGLayer(at: at, background: background, cornerRadius: cornerRadius)
// second layer a blur round corner clip from the image
createIceLayer(at: at, backgroundOpacity: backgroundOpacity, cornerRadius: cornerRadius, blurRadius: blurRadius)
// text on top of the blurred part (use geometry to reset text position)
createTextLayer(at: at, textString: textString, fontSize: fontSize, fontDesign: fontDesign, fontWeight: fontWeight, foregroundColor: foregroundColor)
})
}
}
String extension to calculate text render width and height (without rendering it) so it can be used from within extansion
import Foundation
import UIKit
import SwiftUI
extension String
{
func sizeUsingFont(fontSize: CGFloat, weight: Font.Weight) -> CGSize
{
var uiFontWeight = UIFont.Weight.regular
switch weight {
case Font.Weight.heavy:
uiFontWeight = UIFont.Weight.heavy
case Font.Weight.bold:
uiFontWeight = UIFont.Weight.bold
case Font.Weight.light:
uiFontWeight = UIFont.Weight.light
case Font.Weight.medium:
uiFontWeight = UIFont.Weight.medium
case Font.Weight.semibold:
uiFontWeight = UIFont.Weight.semibold
case Font.Weight.thin:
uiFontWeight = UIFont.Weight.thin
case Font.Weight.ultraLight:
uiFontWeight = UIFont.Weight.ultraLight
case Font.Weight.black:
uiFontWeight = UIFont.Weight.black
default:
uiFontWeight = UIFont.Weight.regular
}
let font = UIFont.systemFont(ofSize: fontSize, weight: uiFontWeight)
let fontAttributes = [NSAttributedString.Key.font: font]
return self.size(withAttributes: fontAttributes)
}
}
and use it like this:
import Foundation
import SwiftUI
struct TestView: View
{
let iceCubePos1: CGRect = CGRect(x: 1100, y: 330, width: 500, height: 200)
let iceCubePos2: CGRect = CGRect(x: 400, y: 130, width: 300, height: 200)
let iceCubePos3: CGRect = CGRect(x: 760, y: 50, width: 200, height: 150)
var body: some View
{
Image("SomeImageFromAssets")
.resizable()
.iceCube(at: iceCubePos1, textString: "Hello again")
.iceCube(at: iceCubePos2, textString: "One", fontSize: 60.0, fontWeight: Font.Weight.heavy, fontDesign: Font.Design.rounded, foregroundColor: Color.black, background: Color.black, backgroundOpacity: 0.8, cornerRadius: 0, blurRadius: 9)
.iceCube(at: iceCubePos3, textString: "U2")
.ignoresSafeArea(.all)
.scaledToFit()
}
}
and it should look like this:
