What is the purpose of .compositingGroup() in SwiftUI? - swiftui

I cannot figure out what compositingGroup() is. At first, I thought it is something like Merging layers in Photoshop. But it was not. Because .shadow() effects to the overlay and background views respectively even if I use .compositingGroup().
So far, I've found 2 differences when I use .compositingGroup()
Text doesn't have shadows.
The shadow size of the overlay view is slightly smaller than the above one.
What is the purpose of compositingGroup?
struct ContentView: View {
var body: some View {
VStack(spacing: 50) {
Text("Without\ncompositing")
.font(.largeTitle)
.bold()
.padding()
.foregroundColor(Color.white)
.background(RoundedRectangle(cornerRadius: 30).fill(Color.red))
.padding()
.padding()
.overlay(RoundedRectangle(cornerRadius: 30).stroke(lineWidth: 10))
.shadow(color: .blue, radius: 5)
Text("With\ncompositing")
.font(.largeTitle)
.bold()
.padding()
.foregroundColor(Color.white)
.background(RoundedRectangle(cornerRadius: 30).fill(Color.red))
.padding()
.padding()
.overlay(RoundedRectangle(cornerRadius: 30).stroke(lineWidth: 10))
.compositingGroup() // <--- I added .compositingGroup() here.
.shadow(color: .blue, radius: 5)
}
}
}

This modifier makes the following modifiers be applied to the view as a whole and not to each particular subview separately
Here's an example to better illustrate this:
struct ContentView: View {
let circles: some View = ZStack {
Circle()
.frame(width: 100, height: 100)
.foregroundColor(.red)
.offset(y: -25)
Circle()
.frame(width: 100, height: 100)
.foregroundColor(.blue)
.offset(x: -25, y: 25)
Circle()
.frame(width: 100, height: 100)
.foregroundColor(.green)
.offset(x: 25, y: 25)
}
var body: some View {
VStack(spacing: 100) {
circles
circles
.opacity(0.5)
circles
.compositingGroup()
.opacity(0.5)
}
}
}
So in your case the shadow is applied to the whole view rather than separately to the Text and overlaying RoundedRectangle

Use it when wanting to apply effects like opacity or shadow to a group of views and not each contained element by itself.

It seems like that .shadow() modifier will add both inner and outer shadow. It means that if the view is not "solid", for example, it has a "hole", .shadow() will add shadow like this:
RoundedRectangle(cornerRadius: 30)
.stroke(lineWidth: 10)
.frame(width: 300)
.shadow(color: .blue, radius: 5)
Click to see the image
So, if you do not want the inner shadow, you need to make your view be "solid", like this:
RoundedRectangle(cornerRadius: 30)
.stroke(lineWidth: 10)
.frame(width: 300)
.background(RoundedRectangle(cornerRadius: 30).fill(.white))
.shadow(color: .blue, radius: 5)
Click to see the image
However, something goes wrong again, the inner shadow doesn't disappear.
That's because I forgot to apply the .compositingGroup() modifier.
As #ramzesenok mentioned, .compositingGroup() makes the following modifiers be applied to the view as a whole and not to each particular subview separately.
So, change the code a little bit:
RoundedRectangle(cornerRadius: 30)
.stroke(lineWidth: 10)
.frame(width: 300)
.background(RoundedRectangle(cornerRadius: 30).fill(.white))
.compositingGroup()
.shadow(color: .blue, radius: 5)
Click to see the image
There is only outer shadow now.

Related

Why does the blur modifier affect views differently?

I spent a lot of time trying to figure out whether this was a bug, but I couldn't for the life of me figure out why a shape wasn't blurring correctly.
VStack {
//Doesn't blur right
Capsule()
.fill(LinearGradient(colors: [Color.red, Color.blue], startPoint: .leading, endPoint: .trailing))
.frame(width: 80, height: 40)
.blur(radius: 10)
//Does blur correctly
HStack {
}
.frame(width: 80, height: 40)
.background(
LinearGradient(colors: [Color.red, Color.blue], startPoint: .leading, endPoint: .trailing)
)
.clipShape(Capsule())
.blur(radius: 10)
}
When you apply blur to an empty HStack, it blurs correctly, while using a blur to a shape doesn't. Can someone explain why it differs?
Firstly, two of them looks like the same but it's not the same.
From Apple docs, Capsule() is subclass of Shape which is subclass of View.
The first one, when you using Capsule() directly then you .fill means just like you add subview LinearGradient with colors into Capsule(). That's the reason I think .blur not working correctly when you call directly from Capsule().
The second one, you .blur correctly because the view was directly be drawn on view ( just like drawing in layer of view in normal UIKit)
The view hierarchy show the different between two of them.
The solution:
You can do like your second view
HStack {
}
.frame(width: 80, height: 40)
.background(
LinearGradient(colors: [Color.red, Color.blue], startPoint: .leading, endPoint: .trailing)
)
.clipShape(Capsule())
.blur(radius: 10)
Or you can make linear gradient with mask
HStack {
LinearGradient(gradient: Gradient(colors: [.red, .blue]), startPoint: .leading, endPoint: .trailing)
.mask(
Capsule()
.blur(radius: 10)
.frame(width: 80, height: 40)
)
}.frame(width: 200, height: 60)
Both will act the same like you draw on the layer of view
UPDATE
If you need it works directly, just use only one color then call .foregroundColor then blur will continue work - But only one color at the time only.
Capsule()
.foregroundColor(Color.red)
.blur(radius: 10)
.frame(width: 80, height: 40)

How to set a Limit for the the views inside a HStack?

Good day, beginner SwiftUI & iOS developer here.
I'm not quite sure how else I could've worded this question, but I'll try my best to explain what I would like to achieve.
Right now, I have a VStack that contains a WebImage and Text view, and this VStack is nested inside a HStack. The views inside the VStack are inside a ForEach loop and are generated dynamically with the data I fetch.
When I display these on a screen, all of these views appear in a single line, as shown below.
However I would like for there to only be max two views per "line", not all four of them stacked into a single line. Is there a way to achieve this?
Here is the code:
HStack(spacing: 20) {
ForEach(attViewModel.students, id: \.self) { student in
VStack {
WebImage(url: URL(string: student.photo))
.resizable()
.aspectRatio(contentMode: .fill)
.frame(width: 40, height: 40)
.clipShape(Circle())
.overlay(Circle().stroke(Color("DarkGreen"), lineWidth: 3))
.compositingGroup()
Text("\(student.name)")
.bold()
.compositingGroup()
CustomRadioButton()
}
.padding()
.overlay(RoundedRectangle(cornerRadius: 10)
.stroke(Color.orange, lineWidth: 2))
.shadow(radius: 7)
}
}
.frame(maxWidth: .infinity)
Here a possible approach for you:
struct ContentView: View {
let arrayOfStudents: [String] = ["jessy", "joy", "joly", "jack"]
var body: some View {
GeometryReader { proxy in
ScrollView(.horizontal) {
HStack(spacing: .zero) {
ForEach(arrayOfStudents, id: \.self) { student in
VStack {
Image(systemName: "person")
.resizable()
.aspectRatio(contentMode: .fit)
.frame(width: 40, height: 40)
.padding()
.clipShape(Circle())
.overlay(Circle().stroke(Color.green, lineWidth: 3))
Text(student)
.bold()
Circle()
.strokeBorder(style: .init(lineWidth: 2))
.frame(width: 10, height: 10)
}
.compositingGroup()
.padding()
.overlay(RoundedRectangle(cornerRadius: 10).stroke(Color.orange, lineWidth: 2))
.shadow(radius: 7)
.padding()
.frame(width: proxy.size.width/2.0)
}
}
}
.position(x: proxy.size.width/2.0, y: proxy.size.height/2.0)
}
}
}

Custom navigation bar's button doesn't work (SwiftUI)

I am currently learning SwiftUI and I followed this video to create a custom NavigationBar.
Learn how to create a custom navigation bar with a logo in SwiftUI framework and Xcode - Part 2
The buttons doesn't work when I click on it unless I don't use padding() and ignoreSafeArea(), but without using it, the navigationBar would appear on middle of the page. (I also tried use Spacer() but it didn't work)
Is there anyway to fix this issue?
Code for HomePage()
VStack{
NavigationBarView()
.padding(.horizontal, 15)
.padding(.bottom)
.padding(.top, UIApplication.shared.windows.first?.safeAreaInsets.top)
.shadow(color: Color.black.opacity(0.1), radius: 5, x: 0, y: 5)
.ignoresSafeArea(.all, edges: .top)
Code for NavigationBarView()
HStack{
NavigationLink(
destination: PersonalMenuPage()
,label: {
ZStack {
Image("Profile")
.renderingMode(.original)
.resizable()
.aspectRatio(contentMode: .fit)
.frame(width: 40, height: 40)
.padding(.horizontal, 10)
Circle()
.fill(Color.red)
.frame(width:14, height: 14, alignment: .center)
.offset(x: 13, y: -10)
}
}
)
}//: hStack
Here is sample output
Sample output

How to detect whether the scrollview was scrolled or not in SwiftUI?

I have a horizontal ScrollView and would like to provide a function that when the first element is not visible anymore an arrow should appear to signalize that there are some other elements.
I've tried the DragGesture() to read the translation.width but it seems to be buggy in combination with the ScrollView.
So I'm looking for a way to detect whether the ScrollView was scrolled and how far. Is there any way?
private let gridItems = [GridItem(.flexible())]
ScrollView(.horizontal, showsIndicators: false) {
HStack{
LazyHGrid(rows: gridItems){
ForEach(viewModel.imageOptions, id: \.self){ image in
ZStack {
Image(image)
.resizable()
.aspectRatio(contentMode: .fit)
.frame(width: 60, height: 60)
.background(Color.white)
.clipShape(Circle())
.background(
Circle()
.clipShape(Circle())
.shadow(color: Color.black.opacity(0.4), radius: 3, x: 5.0, y: 5.0)
)
.onTapGesture {
print("selected")
}
.padding(15)
Circle()
.strokeBorder(Color.orange, lineWidth: 5)
.frame(width: 70, height: 70)
}
}
}
}
}
}

Clipped not actually clips the Image in SwiftUI

I am trying to clip the image and as we see the UI it looks fine but actually it doesn't clip the image which causes other UI elements to unresponsive.
Here the code I am using.
struct ImageContentView: View {
var urls:[String] = [
"https://lh3.googleusercontent.com/proxy/80im-IBfLODpLDj8d02uEpSVIhqdjen6H6CeFwgRBxeua-Dgw0R3WONFj1Gk8CwB_MufmC9rQ8qHjyWMejwFcJ1PA2s8AAu5WVsmJA=s0-d",
"https://wallpaperaccess.com/full/530919.jpg"
]
var body: some View {
ScrollView{
VStack{
Button(action: {
}, label: {
Text("Hello")
})
VStack(spacing: 20.0) {
ForEach(self.urls, id:\.self) { url in
WebImage(url: URL.init(string: url)!)
.resizable()
.aspectRatio(contentMode: ContentMode.fill)
.frame(height: UIScreen.main.bounds.size.width * 0.5)
.clipped()
.cornerRadius(10.0)
.shadow(color: Color.red, radius: 10.0, x: 0, y: 0)
}
}.padding()
}
}
}
}
Here is fixed part (tested with Xcode 12 / iOS 14)
VStack(spacing: 20.0) {
ForEach(self.urls, id:\.self) { url in
WebImage(url: URL.init(string: url)!)
.resizable()
.aspectRatio(contentMode: ContentMode.fill)
.frame(height: UIScreen.main.bounds.size.width * 0.5)
.clipped()
.cornerRadius(10.0)
.shadow(color: Color.red, radius: 10.0, x: 0, y: 0)
}.contentShape(Rectangle()) // << here !!
}.padding()
Note: I don't know what is your WebImage but with Image and local images it was reproduced as well, so fix was tested.
iOS 13 and +
An other solution is combine compositingGroup and clipped:
That wraps this view in a compositing group and clips.
Note: Respect this order compositingGroup and clipped
Image(..)
.compositingGroup()
.clipped()
I have tried for the above methods but still not working.
Here is another solution for someone who have the same struggle, maybe this can help you :) This line is to disable the tap gesture of the image.
Image(uiImage: image)
.resizable()
.aspectRatio(contentMode: .fill)
.frame(width: 100, height: 100)
.contentShape(RoundedRectangle(cornerRadius: 8))
.allowsHitTesting(false) // <- this is the line