I'm having trouble keeping the contents of a ScrollView contained within the scrollview:
Initially, I want to display letters A and B in the ScrollView and have the user scroll to see additional letters. However, even though I've constrained the parent VStack to a frame with height of 120, you can also see the letter C which is outside of the ScrollView (as indicated by the blue background). Here's the code:
var body: some View {
VStack(alignment: .leading, spacing: 0) {
HStack(alignment: .center , spacing: 5) {
Text("Letter").font(.tableHeader).frame(width: 75, height: 30, alignment: .center)
} // HStack
.frame(width: 195, height: 50, alignment: .center)
.background(Color.green)
VStack(alignment: .leading, spacing: 0) {
GeometryReader { outsideProxy in
ScrollView (.vertical, showsIndicators: false) {
ZStack(alignment: .top) {
GeometryReader { insideProxy in
Color.clear
// get offset
} // GeometryReader inside
VStack(alignment: .leading, spacing: 10) {
HStack(alignment: .center, spacing: 5){
Text("A").font(.tableData).frame(width: 75, height: 50, alignment: .center)
}
HStack(alignment: .center, spacing: 5){
Text("B").font(.tableData).frame(width: 75, height: 50, alignment: .center)
}
HStack(alignment: .center, spacing: 5){
Text("C").font(.tableData).frame(width: 75, height: 50, alignment: .center)
}
HStack(alignment: .center, spacing: 5){
Text("D").font(.tableData).frame(width: 75, height: 50, alignment: .center)
}
} // VStack
} // ZStack
} // Scrollview
} // GeometryReader outside
.background(Color.blue)
} // VStack
.frame(width: 195, height: 120, alignment: .leading)
} // VStack
}
The full code requires that use of GeometryReader (and consequently, the ZStack) which is why I've left those items in the sample above.
What is the best way to solve this issue? Open to any improvements for coding the above layout. Keep in mind that ultimately, I want to the user to be able to click on A, B, C, or D to be taken to the next view in the navigation stack.
Make it clipped
ScrollView (.vertical, showsIndicators: false) {
ZStack(alignment: .top) {
GeometryReader { insideProxy in
Color.clear
// get offset
} // GeometryReader inside
VStack(alignment: .leading, spacing: 10) {
HStack(alignment: .center, spacing: 5){
Text("A").font(.tableData).frame(width: 75, height: 50, alignment: .center)
}
HStack(alignment: .center, spacing: 5){
Text("B").font(.tableData).frame(width: 75, height: 50, alignment: .center)
}
HStack(alignment: .center, spacing: 5){
Text("C").font(.tableData).frame(width: 75, height: 50, alignment: .center)
}
HStack(alignment: .center, spacing: 5){
Text("D").font(.tableData).frame(width: 75, height: 50, alignment: .center)
}
} // VStack
} // ZStack
} // Scrollview
.clipped() // << here !!
Related
In my application I want to get simple round buttons based on SF Symbols of the same size. However, the same approach results in different image sizes depending on the symbol.
For example, an image with a plus sign is larger than a minus sign.
To solve this problem, I use the ZStack trick in which I put a transparent plus under the minus. But I think this is not the best solution. Are there any better solutions?
HStack{
Image(systemName: "plus")
.padding()
.overlay(
Circle()
.stroke(Color.primary,
lineWidth:1))
Image(systemName: "minus")
.padding()
.overlay(
Circle()
.stroke(Color.primary,
lineWidth:1))
//my solution
ZStack {
Image(systemName: "plus")
.padding()
.opacity(0.0)
.overlay(
Circle()
.stroke(Color.primary,
lineWidth:1))
Image(systemName: "minus")
}
}
"minus" in the center has a smaller size than "plus", "minus" on the right - my solution:
You can use ViewModifier or if are buttons ButtonStyle
ViewModifier
#available(iOS 13.0, *)
struct fillButtonCircle: ViewModifier {
var foregroundColor: Color = .white
var backgroundColor: Color = .green
var dimension: CGFloat = 10
func body(content: Content) -> some View {
content
.foregroundColor(foregroundColor)
.padding(dimension)
.background(backgroundColor)
.clipShape(Circle())
.frame(width: dimension, height: dimension)
.overlay(
Circle()
.stroke(Color.primary,
lineWidth:1))
}
}
ButtonStyle
#available(iOS 13.0, *)
struct CircleScaleButton: ButtonStyle {
var color: Color = .blue
var maxHeight: CGFloat = 35
public func makeBody(configuration: Self.Configuration) -> some View {
configuration.label
.frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: maxHeight, alignment: .center)
.foregroundColor(.black)
.background(RoundedRectangle(cornerRadius: 35/2.0).fill(self.color))
.compositingGroup()
.clipShape(Circle())
.overlay(
Circle()
.stroke(Color.primary,
lineWidth:1))
.opacity(configuration.isPressed ? 0.8 : 1.0)
.scaleEffect(configuration.isPressed ? 0.9 : 1.0)
}
}
Example
struct SwiftUIViewTest: View {
var body: some View {
VStack {
Text("Button")
HStack {
Button(action: {}, label: {
Image(systemName: "plus")
})
.buttonStyle(CircleScaleButton(color: .clear, maxHeight: 45))
Button(action: {}, label: {
Image(systemName: "minus")
})
.buttonStyle(CircleScaleButton(color: .clear, maxHeight: 45))
}
Spacer()
.frame(height: 50)
Text("Image")
HStack {
Image(systemName: "plus")
.modifier(fillButtonCircle(foregroundColor: .black, backgroundColor: .clear, dimension: 40))
Image(systemName: "minus")
.modifier(fillButtonCircle(foregroundColor: .black, backgroundColor: .clear, dimension: 40))
}
}
}
}
use .circle
import SwiftUI
struct ContentView: View {
var body: some View {
HStack {
Image(systemName: "plus.circle")
.resizable()
.frame(width: 50, height: 50, alignment: .center)
Image(systemName: "minus.circle")
.resizable()
.frame(width: 50, height: 50, alignment: .center)
}
}
}
I am developing a cross platform app in SwifUI. On iPhone / iPad this code works really well on MacOS instead when I insert a NavigationLink the ForecastCardView is totally cut off. When I remove the NavigationLink everything is rendered correctly.
With NavigationLink
var FullSection: some View {
LazyVGrid(columns: homeConfigurationUI.columns, alignment: .center, spacing: 20) {
NavigationLink(destination: Text("test")) {
ForecastCardView(viewModel: ForecastCardViewModel.initForTest())
}.frame(width: 300, height: 300, alignment: .center)
}
.border(Color.yellow)
.frame(minWidth: 300, idealWidth: 400, maxWidth: 600, minHeight: 0, idealHeight: 500, maxHeight: .infinity, alignment: .center)
}
Image With NavigationLink
Without NavigationLink
var FullSection: some View {
LazyVGrid(columns: homeConfigurationUI.columns, alignment: .center, spacing: 20) {
ForecastCardView(viewModel: ForecastCardViewModel.initForTest())
}
.border(Color.yellow)
.frame(minWidth: 300, idealWidth: 400, maxWidth: 600, minHeight: 0, idealHeight: 500, maxHeight: .infinity, alignment: .center)
}
Image Without NavigationLink
Everything is inside a ScrollView, I tried to insert a List a VStack, but no results. I tried to put a static frame on every single component, but nothing to do.
You have to set buttonStyle to PlainButtonStyle():
var FullSection: some View {
LazyVGrid(columns: homeConfigurationUI.columns, alignment: .center, spacing: 20) {
NavigationLink(destination: Text("test")) {
ForecastCardView(viewModel: ForecastCardViewModel.initForTest())
}
.buttonStyle(PlainButtonStyle()) // <— HERE
.frame(width: 300, height: 300, alignment: .center)
}
.border(Color.yellow)
.frame(minWidth: 300, idealWidth: 400, maxWidth: 600, minHeight: 0, idealHeight: 500, maxHeight: .infinity, alignment: .center)
}
For a macOS app, I like to extend a button's width across it's parent view using SwiftUI. I tried this:
import SwiftUI
struct ContentView: View {
var body: some View {
VStack {
Button(action: {print("TEST")}) {
Image(systemName: "clock")
.frame(width: 25, height: 25, alignment: .center)
Text("This is a button")
.frame(minWidth: 200, idealWidth: 200, maxWidth: .infinity, minHeight: 25, idealHeight: 25, maxHeight: 25, alignment: .leading)
Image(systemName: "chevron.left")
.frame(width: 25, height: 25, alignment: .center)
}
.buttonStyle(PlainButtonStyle())
}
}
}
and it will yield a result that looks quite what I am searching, but if you click in the area marked in red in the following screenshot of the app
the buttons action does not fire. Also the area between the first image and the text does not fire the action. I also tried to implement a custom ButtonStyle but with no luck. How can I achieve a button that extends over the full width of the parent view?
You can try using .contentShape(Rectangle()):
Button(action: { print("TEST") }) {
HStack {
// ...
}
.frame(maxWidth: .infinity)
.contentShape(Rectangle())
}
I have an Image, and I want it to rotate on the y axis, and move towards the bottom of the View when I press a Button. I tried using the .onChange, but I get the error "Result of call to 'animation' is unused", of which I understand the meaning, but I don't understand neither why it comes up nor how can I fix it.
Here's my code:
import SwiftUI
struct ContentView : View {
#State var animateCoin = false
#Environment(\.colorScheme) var colorScheme
var body: some View {
Rectangle()
.foregroundColor(colorScheme == .dark ? .white : .black)
.frame(width: 150, height: 150, alignment: .center)
.offset(y: -60)
.mask(Image("Coin") //That's the name of the Image I have in the assets
.resizable()
.onChange(of: animateCoin, perform: { value in
self.animation(.easeIn) //I put .easeIn just as an example, because the .rotation3DEffect gives me a red warning
}))
Button(action: { animateCoin = true } ) {
ZStack {
RoundedRectangle(cornerRadius: 10)
.foregroundColor(.green)
.frame(width: 100, height: 40, alignment: .center)
.shadow(radius: 10)
Text("Animate")
.foregroundColor(.white)
.shadow(radius: 20)
}
}
}}
The Image is set as a mask so that I can easily control its color depending on the light or dark mode.
Thank to everyone who will help me!
How about doing it like this:
#State var animateCoin = false
#Environment(\.colorScheme) var colorScheme
var body: some View {
VStack {
Rectangle()
.foregroundColor(colorScheme == .dark ? .white : .black)
.frame(width: 150, height: 150, alignment: .center)
.offset(y: -60)
.mask(Image(systemName: "car.fill")
.resizable()
.aspectRatio(contentMode: .fit)
.rotation3DEffect(
Angle(degrees: self.animateCoin ? 360 : 0),
axis: (x: 0, y: self.animateCoin ? 360 : 0, z: 0)
)
)
.offset(y: self.animateCoin ? 600 : 0)
.animation(.linear(duration: 1))
ZStack {
Button(action: { self.animateCoin.toggle() } ) {
ZStack {
RoundedRectangle(cornerRadius: 10)
.foregroundColor(.green)
.frame(width: 100, height: 40, alignment: .center)
.shadow(radius: 10)
Text("Animate")
.foregroundColor(.white)
.shadow(radius: 20)
}
}
}
}
}
As you asked:
Rotate on Y axis.
Move the image to the buttom.
I am trying to create a basic lazyHGrid but after looking at a couple of different tutorials online I still can't manage to build it properly. I am trying to make a grid that is filled with rectangles with symbols and text inside those rectangles. See image below:
If I make this in a HStack all the rectangles are presented correctly, but as soon as I put it into a LazyHGrid I get this problem. Any ideas?
let rows = [
GridItem(.fixed(200)),
GridItem(.fixed(200)),
]
var product: ProductModel
var body: some View {
LazyHGrid(rows: rows, alignment: .center) {
ForEach(0..<product.imageStack01Image.count, id: \.self) { item in
ZStack(alignment: .top){
RoundedRectangle(cornerRadius: 25, style: .continuous)
.fill(Color.white)
.frame(width: 200, height: 200)
.shadow(color: Color.black.opacity(0.08), radius: 20, x: 0, y: 5)
VStack(alignment: .leading) {
Image(systemName:product.imageStack01Image[item])
.font(.system(size: 40.0))
.frame(height: 40)
.foregroundColor(.red)
.padding(.top, 40)
.padding(.leading, 10)
Text(product.imageStack01Text[item])
.font(.title2)
.fontWeight(.medium)
.multilineTextAlignment(.trailing)
.padding(10)
}
}
}
}
}
As far as I understood your try to put image and text into rectangle, so try to use overlay instead of ZStack
ForEach(0..<product.imageStack01Image.count, id: \.self) { item in
RoundedRectangle(cornerRadius: 25, style: .continuous)
.fill(Color.white)
.frame(width: 200, height: 200)
.shadow(color: Color.black.opacity(0.08), radius: 20, x: 0, y: 5)
.overlay(
VStack(alignment: .leading) {
Image(systemName:product.imageStack01Image[item])
.font(.system(size: 40.0))
.frame(height: 40)
.foregroundColor(.red)
.padding(.top, 40)
.padding(.leading, 10)
Text(product.imageStack01Text[item])
.font(.title2)
.fontWeight(.medium)
.multilineTextAlignment(.trailing)
.padding(10)
})
}
}
It might have to do with the hardcoded frame in your square. Try using aspectRatio if you just want a square or frame each layer of the ZStack or frame the ZStack depending on what you want to achieve.
import SwiftUI
struct LazyGridSample: View {
///Sample Products to reproduce
#State var products = ["0xxxxxxxxxx0xxxxxxxxxx","1xxxxxxxxxx0xxxxxxxxxx","2xxxxxxxxxx0xxxxxxxxxx","3xxxxxxxxxx0xxxxxxxxxx","4xxxxxxxxxx0xxxxxxxxxx","5xxxxxxxxxx0xxxxxxxxxx","6xxxxxxxxxx0xxxxxxxxxx","7xxxxxxxxxx0xxxxxxxxxx","8xxxxxxxxxx0xxxxxxxxxx","9xxxxxxxxxx0xxxxxxxxxx"]
let rows = [
GridItem(.fixed(200)),
GridItem(.fixed(200)),
]
let squareSize: CGSize = CGSize(width: 200, height: 200)
var body: some View {
//ScrollView(.horizontal){
LazyHGrid(rows: rows, alignment: .center) {
//HStack{
ForEach(0..<products.count, id: \.self) { idx in
ZStack(alignment: .top){
RoundedRectangle(cornerRadius: 25, style: .continuous)
.fill(Color.white)
//.frame(width: squareSize.width, height: squareSize.height)
//.aspectRatio(1, contentMode: .fill)
.shadow(color: Color.black.opacity(0.08), radius: 20, x: 0, y: 5)
VStack(alignment: .leading) {
//Removed Image
Text(products[idx].description)
.font(.title2)
.fontWeight(.medium)
.multilineTextAlignment(.trailing)
.padding(10)
}//.frame(width: squareSize.width, height: squareSize.height, alignment: .center)
//.aspectRatio(1, contentMode: .fill)
}//Adjusts the the entire ZStack
//.frame(width: squareSize.width, height: squareSize.height, alignment: .center)
.aspectRatio(1, contentMode: .fill)
}
}
//}//ScrollView
}
}
struct LazyGridSample_Previews: PreviewProvider {
static var previews: some View {
LazyGridSample()
}
}