SwiftUI show collection of images - swift

I would like to show a grid of images similar to UICollectionView like this:
With each color representing an image.
When I do the following:
struct ContentView: View {
var body: some View {
ScrollView{
ForEach(0..<8) { i in
HStack {
ForEach(0..<3) { j in
ZStack(alignment: .center) {
Image("image")
.background(Color.random)
.clipped()
.aspectRatio(contentMode: .fill)
}
.frame(width: 120, height: 120)
.background(Color.random)
}
}
}
}
}
}
I get one image which is not bound by its frame and therefore fills the whole screen.

Set a frame and resizable modifier to you image.
Image("image")
.resizable()
.frame(width: 120, height: 120)
Full example
struct ContentView: View {
var body: some View {
ScrollView{
ForEach(0..<8) { i in
HStack {
ForEach(0..<3) { j in
ZStack(alignment: .center) {
Image("image")
.resizable()
.frame(width: 120, height: 120)
}
.frame(width: 120, height: 120)
.background(Color.blue)
}
}
}
}
}
}
Produces this

Related

SwiftUI, add inverted Opacity

Please see the attach image below, I want to the make this view in siwftUI that have zero opacity inside.
struct TestView: View {
var body: some View {
ZStack{
// any dummy image
Image("bigpancake")
.resizable()
ZStack {
// Make this view opcity zero so that I can see backgourd clearly
Rectangle()
.frame(width: 200, height: 100)
.foregroundColor(.black.opacity(0))
Rectangle()
.foregroundColor(.black.opacity(0.3))
}
}
}
}
If I understand correctly what you are looking for, it sounds like an "inverted mask". You can achieve what you want using compositingGroup()
Example:
struct ContentView: View {
var body: some View {
ZStack {
Image("background")
.resizable()
.aspectRatio(contentMode: .fill)
.ignoresSafeArea()
ZStack {
Rectangle()
.foregroundColor(.black.opacity(0.3))
.ignoresSafeArea()
Rectangle()
.cornerRadius(20)
.frame(width: 200, height: 200)
.blendMode(.destinationOut)
.overlay {
Text("Content")
}
}.compositingGroup()
}
}
}

SwiftUI scaled background intercepting clicks

I'm encountering an issue with SwiftUI on macOS (12.x) where a scaled background is interfering with mouse clicks. The following is a minimal example:
struct ContentView: View {
var body: some View {
VStack {
Button("Test") {
print("Tested")
}
Image(systemName: "waveform.circle")
.resizable()
.frame(width: 200, height: 200)
.background(
Image(systemName: "waveform.circle")
.resizable()
.foregroundColor(.red)
.scaleEffect(2.0)
.blur(radius: 20)
.clipped()
)
}
.frame(width: 500, height: 500)
}
}
Note that pressing the "Test" button doesn't actually work -- no message to the console is printed.
I've found a workaround using NSHostingView, but it's pretty ugly -- I'd love to know if there's a pure-SwiftUI solution to 'clip' the background View so that not only is it's appearance cut off, but also it's ability to intercept clicks.
Here's a workaround (note that turning off the scale effect also solves the issue).
struct ContentViewWorkAround: View {
#State private var scaleEffectOn = true
#State private var workAround = false
#ViewBuilder private var imageBackground: some View {
switch workAround {
case false:
Image(systemName: "waveform.circle")
.resizable()
.foregroundColor(.red)
.scaleEffect(scaleEffectOn ? 2.0 : 1.0)
.blur(radius: 20)
.clipped()
.frame(width: 200, height: 200) // ineffective at preventing clicks
case true:
NSHostingViewRepresented {
Image(systemName: "waveform.circle")
.resizable()
.foregroundColor(.green)
.scaleEffect(2.0)
.blur(radius: 20)
.clipped()
.frame(width: 200, height: 200)
}
}
}
var body: some View {
VStack {
Button("Test") {
print("Tested")
}
Image(systemName: "waveform.circle")
.resizable()
.frame(width: 200, height: 200)
.background(
imageBackground
)
Toggle("Scale effect", isOn: $scaleEffectOn)
Toggle("Workaround", isOn: $workAround)
}
.frame(width: 500, height: 500)
}
}
struct NSHostingViewRepresented<V>: NSViewRepresentable where V: View {
var content: () -> V
func makeNSView(context: Context) -> NSHostingView<V> {
NSHostingView(rootView: content())
}
func updateNSView(_ nsView: NSHostingView<V>, context: Context) { }
}
Have you tried?
.allowsHitTesting(false)
Deactivates the clicks and allows whatever look you want.
struct ContentView: View {
var body: some View {
VStack {
Button("Test") {
print("Tested")
}
Image(systemName: "waveform.circle")
.resizable()
.frame(width: 200, height: 200)
.background(
Image(systemName: "waveform.circle")
.resizable()
.foregroundColor(.red)
.scaleEffect(2.0)
.blur(radius: 20)
.clipped()
.allowsHitTesting(false)
)
}
.frame(width: 500, height: 500)
}
}

SwiftUI Shape Scale Size Such that HStack size does not increase

I'm trying to make the circles fit into the HStack such that the HStack size does not increase.
How can I make the circles fit without specifying a fixed frame?
struct ContentView: View {
var body: some View {
NavigationView {
Form {
HStack {
Circle()
.fill(Color.red)
.aspectRatio(1, contentMode: .fit)
Text("Hello")
}
HStack {
Circle()
.fill(Color.blue)
.aspectRatio(1, contentMode: .fit)
Text("Hello")
}
}
}
}
}
Here is a sample of various containers to chose from. SwiftUI will do all the layout, automatically handle rotations and device resolutions.
struct CirclesView: View {
var body: some View {
VStack(spacing: 0) {
Label("Circles", systemImage: "circle").font(.system(size: 24, weight: .black, design: .rounded)).foregroundColor(.pink)
HStack {
Circle()
.foregroundColor(.yellow)
.frame(width: 32, height: 32)
Text("This is a yellow circle")
Spacer()
}
Circle()
.foregroundColor(.orange)
.shadow(radius: 10)
.frame(width: 75)
Divider()
HStack {
VStack {
Circle().foregroundColor(.blue)
Text("Blue").font(.title3)
HStack {
Circle().foregroundColor(.purple)
Text("Purple").font(.caption)
}
}
.padding()
.background(Color.yellow)
ZStack(alignment: Alignment(horizontal: .center, vertical: .center)) {
Circle().foregroundColor(.green)
Text("Green").foregroundColor(.primary)
}
}
}
}
}

Make SwiftUI Rectangle same height or width as another Rectangle

For a SwiftUI layout in a macOS app, I have three Rectangles as shown below:
The code to produce this layout is:
import SwiftUI
struct ContentView: View {
var body: some View {
VStack {
HStack {
ZStack {
Rectangle()
.fill(Color.purple)
.frame(width: 20)
Text("1")
.font(.subheadline)
.foregroundColor(.white)
}
ZStack {
Rectangle()
.fill(Color.orange)
Text("2")
.font(.subheadline)
.foregroundColor(.white)
}
}
HStack {
ZStack {
Rectangle()
.fill(Color.red)
.frame(height: 20)
Text("3")
.font(.subheadline)
.foregroundColor(.white)
}
}
}
.frame(minWidth: 400, minHeight: 250)
}
}
My objective is for Rectangle 1 to be the same height as Rectangle 2 and for Rectangle 3 to be the same width as Rectangle 2. The size relationships between the rectangles should stay the same as the window size is changed. When done correctly, the final result should look like the following:
How can I accomplish this in SwiftUI?
Here is a working approach, based on view preferences. Tested with Xcode 11.4 / macOS 10.15.6
struct ViewWidthKey: PreferenceKey {
typealias Value = CGFloat
static var defaultValue: CGFloat { 0 }
static func reduce(value: inout Value, nextValue: () -> Value) {
value = value + nextValue()
}
}
struct ContentView: View {
#State private var boxWidth = CGFloat.zero
var body: some View {
VStack {
HStack {
ZStack {
Rectangle()
.fill(Color.purple)
.frame(width: 20)
Text("1")
.font(.subheadline)
.foregroundColor(.white)
}
ZStack {
Rectangle()
.fill(Color.orange)
Text("2")
.font(.subheadline)
.foregroundColor(.white)
}
.background(GeometryReader {
Color.clear.preference(key: ViewWidthKey.self,
value: $0.frame(in: .local).size.width) })
}
HStack {
ZStack {
Rectangle()
.fill(Color.red)
.frame(height: 20)
Text("3")
.font(.subheadline)
.foregroundColor(.white)
}.frame(width: boxWidth)
}.frame(maxWidth: .infinity, alignment: .bottomTrailing)
}
.onPreferenceChange(ViewWidthKey.self) { self.boxWidth = $0 }
.frame(minWidth: 400, minHeight: 250)
}
}

How to show or hide an overlay at a Text or Button

I do have a few items that I need to hide ( not disable ) or show depending on a value.
As for a Text() or BUtton() sample, I need to have a overlay or no overlay.
Button("how secret?", action: {
self.secretOverlay = true
})
.overlay( TopSecretOverlayView()
....
})
I did try something like
struct TopSecretOverlayView: View {
var body: some View {
HStack {
if secretOverlay {
Text("Top Secret")
.bold()
.font(.system(size: 64))
.frame(width: 350, height: 80, alignment: .center)
} else {
....
}
}
}
}
.presentation is deprecated. Not sure if that was the way.
But how should I switch a overlay between hidden and visable?
Where should an if statement look like?
As always, thank you!
Have you tried this:
Button("how secret?") {
showSecretOverlay = true
}
.overlay(TopSecretOverlayView().opacity(showSecretOverlay ? 1 : 0))
Another way of handling it is with a view builder:
struct SecretView: View {
#State private var showSecretOverlay = false
var body: some View {
Button("show password?") {
showSecretOverlay.toggle()
}
.overlay(secretOverlay)
}
#ViewBuilder private var secretOverlay: some View {
if showSecretOverlay {
TopSecretOverlayView()
}
}
}
struct TopSecretOverlayView: View {
var body: some View {
Text("password")
.foregroundColor(.white)
.background(.black)
}
}
struct SecretView_Previews: PreviewProvider {
static var previews: some View {
SecretView()
}
}
If the button tap will always have the same action, then how about setting the opacity to 0 if you want it invisible and a 1 if you want it visible?
I know this may be a little old but it seems like you could probably do what you want using an ObservedObject passed into the view.
struct TopSecretOverlayView: View {
#ObservedObject var secretVM: SecretViewModel
var body: some View {
HStack {
switch secretVM.accessType {
case .top:
Text("Top Secret")
.bold()
.font(.system(size: 64))
.frame(width: 350, height: 80, alignment: .center)
case .middle:
Text("Secret")
.bold()
.font(.system(size: 64))
.frame(width: 350, height: 80, alignment: .center)
case .low:
Text("Common Knowledge")
.bold()
.font(.system(size: 64))
.frame(width: 350, height: 80, alignment: .center)
default:
Text("Access Denied")
.bold()
.font(.system(size: 64))
.frame(width: 350, height: 80, alignment: .center)
}
}
}
}
Then where ever you change the accessType property it should cause it to update the overlay.
Button("how secret?", action: {
self.secretVM.accessType = .top
})
.overlay( TopSecretOverlayView(secretVM:self.secretVM)
....
})