How to mask out a region of a view - view

I am looking for advice on the best way to achieve the following with SwiftUI.
I have a view with a red rounded rectangle corner, and a child view which is a rectangular blue box offset to the bottom of the parent. 1
However, I wish to mask out the the hatched area in the second attached figure 2 so that is appear white (i.e. remove the blue hatched area) and am not sure how to best accomplish this.
This is the code as it stands:-
import SwiftUI
import Foundation
struct PupilCell : View {
var body : some View {
ZStack {
Rectangle().frame(height: 60.0, alignment: .bottom).foregroundColor(Color.blue).offset(x: 0.0, y: 50.0)
RoundedRectangle(cornerRadius: 25, style: .continuous).stroke(lineWidth: 2.0).fill(Color.red)
}
}
}

Here is possible approach:
ZStack {
Rectangle().frame(height: 60.0, alignment: .bottom).foregroundColor(Color.blue).offset(x: 0.0, y: 50.0)
RoundedRectangle(cornerRadius: 25, style: .continuous).stroke(lineWidth: 2.0).fill(Color.red)
}.mask(RoundedRectangle(cornerRadius: 25, style: .continuous).fill(Color.black))

Related

How to scale an image to fill the parent view without affecting the layout in SwiftUI?

Goal
I often have the case where I want to scale an image so that it fills its container proportionally without modifying its frame. An example is seen in the first screenshot below. The container view has a fixed frame as indicated by the red border. The image itself has a different aspect ratio. It's scaled to fill the entire container, but it still has the same frame as the container and thus does not affect the container's layout (size).
My Solution
I used the following code to accomplish this:
struct ContentView: View {
var body: some View {
ImageContainerView()
.frame(width: 300, height: 140)
.border(.red, width: 2)
}
}
struct ImageContainerView: View {
var body: some View {
GeometryReader { geometry in
Image("image")
.resizable()
.aspectRatio(contentMode: .fill)
.border(.blue, width: 2)
.frame(width: geometry.size.width, height: geometry.size.height)
.clipped()
}
}
}
Problem
As you can see, I used a geometry reader to manually set the image's frame to match the parent view's frame. This feels a bit superfluous as the image already receives this information implicitly as the proposed size from its parent view. However, if I don't use the geometry reader this way, the image is not clipped and its frame matches the full scaled image, not the parent view's frame. This is shown in the second screenshot below.
Question
Is there a (more "native") way in SwiftUI to achieve the desired behavior without using a geometry reader?
Here is a way to do it without using GeometryReader. By making the image an .overlay() of another view, that view can handle the clipping with the .clipped() modifier:
struct ContentView: View {
var body: some View {
ImageContainerView()
.frame(width: 300, height: 140)
.border(.red, width: 2)
}
}
struct ImageContainerView: View {
var body: some View {
Color.clear
.overlay (
Image("image")
.resizable()
.aspectRatio(contentMode: .fill)
.border(.blue, width: 2)
)
.clipped()
}
}
Also, pass in the name of the image so that ImageContainerView and be reused with other images.
Just move .clipped() from the child view to the parent. That is where you want the image clipped:
struct ContentView: View {
var body: some View {
ImageContainerView()
.frame(width: 300, height: 140)
.border(.red, width: 2)
.clipped() // Here
}
}
struct ImageContainerView: View {
var body: some View {
Image("island")
.resizable()
.aspectRatio(contentMode: .fill)
.border(.blue, width: 2)
}
}

SwiftUI TextField color issue with background and foreground

I am trying to set background color to my textfield and it seems to be setting fine for padding area around the textfield but not to the actual textfield
struct PrimaryTextField: View {
var initialText: String
#Binding var key: String
var body: some View {
VStack {
TextField(initialText, text: $key)
.padding()
.font(.title3)
.frame(maxWidth: .infinity)
.background(Color("TextFieldBGColor"))
.cornerRadius(50.0)
.shadow(color: Color.black.opacity(0.08), radius: 60, x: 0.0, y: 16)
.accentColor(Color("AccentColor"))
.textFieldStyle(.roundedBorder)
}
}
}
Above is the coded i used. Below are the images
Light mode
dark mode
How to make the white text field to be gray colored textfield (in light mode)?
I tried foreground color to TextFieldBGColor but it is changing the color of text in the text field which i dont want to change.
SwiftUI's TextField holds a TextFieldStyle property which controls certain aspects of the UI of the TextField - this is what is responsible for the border and added background on your TextView. Not sure if this is exactly the effect that you're looking for, but you can simply add the modifier .textFieldStyle to your TextField to remove the added styling, which will remove the TextField's background color. If you want the border that was originally there, you may have to add that back as custom using the .background modifier.
TextField(initialText, text: $key)
.textFieldStyle(.plain) <------ Added here
.padding()
.font(.title3)
.frame(maxWidth: .infinity)
.background(Color("TextFieldBGColor"))
.cornerRadius(50.0)
.shadow(color: Color.black.opacity(0.08), radius: 60, x: 0.0, y: 16)
.accentColor(Color("AccentColor"))
.textFieldStyle(.roundedBorder)
Read more on the TextFieldStyle protocol here:
https://developer.apple.com/documentation/swiftui/textfieldstyle

Calculate/Predict the future center of a frame with SwiftUI

Swift 5.2, iOS 13
I want to predict/calculate the centre point of a frame after it has been scaled so I can move a shape to it while scaling it. If I try to scale and center an view in an animation/dynamically it doesn't work, with the end result a combination of center points I suspect. So in the images below, the blue box starts in the top right hand corner, I scale and move it to the center. But as you can see from the green box, the scaling has messed up the point it needs to get to...
struct SwiftUIView2: View {
#State var relocate = Alignment.topTrailing
#State var zoom:CGFloat = 1.0
#State var tag:Bool = true
#State var tag2:Bool = false
#State var centerPoint: CGPoint = .zero
var body: some View {
ZStack {
ZStack(alignment: relocate) {
Rectangle()
.stroke(Color.blue, lineWidth: 2)
.frame(width: 32, height: 32, alignment: .center)
.onTapGesture {
withAnimation {
if self.tag {
self.zoom = 2.0
self.relocate = Alignment.center
} else {
self.relocate = Alignment.topTrailing
self.zoom = 1.0
}
}
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
self.tag.toggle()
}
}
.scaleEffect(zoom, anchor: .topLeading)
}.frame(width: 256, height: 256, alignment: relocate)
.border(Color.red)
if !tag {
ZStack {
Rectangle()
.stroke(Color.green, lineWidth: 2)
.frame(width: 32, height: 32, alignment: .center)
.onTapGesture {
self.tag.toggle()
}
}.frame(width: 256, height: 256, alignment: .center)
.scaleEffect(zoom, anchor: .center)
}
}
}
}
GeoReader I thought would be the answer, but I get garbage from it too. Spent more than a week trying custom alignments, position, everything I can think of. Eyeballed a solution with screen site percentages for now, but obviously it doesn't work too well with different sized screens.
Here is fix (tested with Xcode 11.4 / iOS 13.4)
}
.scaleEffect(zoom) // << here !! (remove topLeading anchor)
}.frame(width: 256, height: 256, alignment: relocate)

SwiftUI pin Image to top right of screen and resize based on device size

I just learning Apple's SwiftUI and it is a bit frustrating even doing the basics. I am trying to get my image to the top right of my screen but the default has it showing up in the center of my screen. Anyone know how I can accomplish this. In addition, I tried to get the image to resize based on the screen size but the old self.frame.width that I used to use in regular Swift doesn't work in Swift UI. Sorry, but this new way of writing code in SwiftUI is very odd to me.
var body: some View {
ZStack {
//Define a screen color
LinearGradient (gradient: Gradient(colors:[Color(ColorsSaved.gitLabDark),Color(ColorsSaved.gitLabLight)]),startPoint: .leading,endPoint: .trailing)
//Extend the screen to all edges
.edgesIgnoringSafeArea(.all)
VStack(alignment: .trailing) {
Image("blobVectorDark")
.resizable()
.edgesIgnoringSafeArea(.top)
.frame(width: 60, height: 60)
// .offset(x: , y: 20)
}
}
}
}
You need to add a frame for the outer container, here is the VStack.
Then assign the alignment for this container.
The width and height in the frame of VStack needs to use geometryProxy.
GeometryReader{ (proxy : GeometryProxy) in // New Code
VStack(alignment: .trailing) {
Image("blobVectorDark")
.resizable()
.edgesIgnoringSafeArea(.top)
.frame(width: 60, height: 60)
// .offset(x: , y: 20)
}
.frame(width: proxy.size.width, height:proxy.size.height , alignment: .topLeading) // New Code
}
I'm learning too so this might not be the ideal way to do it, but I did manage to get the image to pin to the top right of the screen.
You can get the screen width from GeometryReader. The alignment was driving me nuts for a while, until I realized I could give a frame to a Spacer to take up the other side of an HStack, and then it worked.
var body: some View {
GeometryReader { geometry in
VStack {
HStack {
Spacer()
.frame(width: geometry.size.width / 2)
Image("blobVectorDark")
.resizable()
.aspectRatio(contentMode: .fit)
.frame(width: geometry.size.width / 2)
}
Spacer()
}
}
.edgesIgnoringSafeArea(.all)
}

How to hover a shape in swiftUI and not the frame containing the shape

Any idea on how to dismiss the hover event for areas that are not covered by a random shape?
Here is the code for the gif:
Circle()
.fill(self.hover ? Color.blue : Color.red)
.frame(width: 100, height:100)
.clipShape(Circle())
.onHover { _ in self.hover.toggle() }
.onTapEvent is able to distinguish frame from shape 😊

Resources