I'm attempting to alter the anchor point for what determines the center of a Button. The following code puts the button at the top left corner of the frame.
Button(action: {
print(self.note)
}) {
Text(note)
}
.position(x: 0.0, y: 0.0)
If I use .offset instead, then it will work. I would like it to be centered within it's frame though. Is there a way to change the anchor point?
You may need to turn on the frame of the parent container, so that you can use frame alignment.
var body: some View{
GeometryReader{ p in
VStack{
Button(action: {
}) {
Text("note")
}
}.frame(width: p.size.width, height: p.size.height, alignment: .topLeading)
}
}
Here is another rough but faster version with AlignmentGuide.
var body: some View{
VStack(alignment: .leading){
Text("")
Button(action: {
}) {
Text("simple version button")
}.background(Color.red).alignmentGuide(.leading) { v in
return -v[.trailing]
}}.position()
}
Hope you can have a better answer.
Related
I ma having an unusual issue.I have a loading screen where the center image is suppose to give a heart beat animation. I am accomplishing this by using the scaleEffect. This works fine in iOS 16. However, in iOS 15 (and even 15.4) The animation is wrong.
What is happening is that the animation's reference point is in the top-left corner instead of the picture's center. Additionally, it appears that the entire view is animating rather then the picture.
Coud I get some feedback on my code? I believe that I am setting something incorrectly or maybe there is a better way of accomplishing what I want.
The issue is happening in the LoadingView struct
struct LoadingView: View{
#State private var animatingLogo = false
var body: some View{
// GeometryReader{geoReader in
VStack(alignment: .center, spacing: 6) {
Image("JHLogo")
.frame(width: 200, height: 200, alignment: .center)
.aspectRatio(contentMode: .fill)
.scaleEffect(animatingLogo ? 0.15 : 0.1, anchor: .center)
.onAppear{
withAnimation(.easeInOut(duration: 1).repeatForever()){
animatingLogo.toggle()
}
}
}
.navigationBarHidden(true)
}
}
And here is the top level view in case this is the one that has the issue:
// Initial view that is loaded on app startup
struct MainView: View{
#State private var isLoading = false
var body : some View {
GeometryReader{geoReader in
NavigationView {
if (isLoading) {
LoadingView()
}
else {
CoverPageView()
}
}
.frame(width: geoReader.size.width, height:geoReader.size.height)
.fixedSize()
.aspectRatio(contentMode: .fill)
.onAppear {
startLoadingScreen()
}
}
}
func startLoadingScreen() {
isLoading = true;
DispatchQueue.main.asyncAfter(deadline: .now() + 3){
isLoading = false
}
}
}
I have a transition between two texts I want to mimic with a SwiftUI transition. this is my reference:
in SwiftUI I have the following code:
Text(model.title)
.transition(.opacity.animation(.default).combined(with: .move(edge: .bottom)))
.id("Title" + model.title)
.animation(.default)
and this is the result:
how could I approach this?
In the reference animation, by the time the text starts to slide it's already close to zero opacity and doesn't really move away from it's frame.
In swiftUI it clearly come from below the current text and moves up
I think .offset instead of .move should do the job:
struct ContentView: View {
let title = ["INITIAL ITEM", "NEW ITEM"]
#State private var i = 0
var body: some View {
VStack {
Spacer()
ZStack {
Text(title[i])
.font(.largeTitle)
.fontWeight(.bold)
.id(i)
.transition(
.offset(x: 0, y: 5)
.combined(with: .opacity)
)
}
Spacer()
Button("Toggle") {
withAnimation(.linear(duration: 0.5)) {
i = 1-i
}
}
}
}
}
I'm trying to animate an image inside a "row" by it's y axis so that it appears that you would the view would slowly scroll vertically through the entire image. And when done, it backtracks. I hope that makes sense. I'm trying to do it in this code:
var body: some View {
ZStack {
HStack {
GeometryReader { geometry in
WebImage(url: self.url)
.renderingMode(.original)
.resizable()
.placeholder {
ImageStore.shared.image(name: "exploras-icon")
}
.aspectRatio(contentMode: .fill)
.animate {
// animate by y offset within bounds of HStack
}
}
}
.frame(height: 140)
.clipped()
}
}
Any help/guidance much appreciated!
Here is a demo of possible approach. Tested with Xcode 11.4 / iOS 13.4
struct TestAutoScrollImage: View {
#State private var isActive = false
var body: some View {
GeometryReader { gp in
HStack {
Image("large_image")
}
.frame(width: gp.size.width, height: gp.size.height, alignment: self.isActive ? .bottom : .top)
}
.edgesIgnoringSafeArea([.top, .bottom])
.animation(Animation.linear(duration: 5).repeatForever())
.onAppear {
self.isActive = true
}
}
}
I had a layout that essentially looked like this:
ZStack(alignment: .bottom) {
GeometryReader { geometry in
ZStack {
Text("Centered")
}
.frame(width: geometry.size.width, height: geometry.size.height, alignment: .center)
.background(Color.red)
}
Group {
GeometryReader { geometry in // This GeometryReader is causing issues.
VStack {
Text("I want this at the bottom")
}
.frame(width: geometry.size.width, height: nil, alignment: .topLeading)
}
}
}
When this is rendered, both Text elements are rendered in the center of the screen. The second text element's container takes up the entire width of the screen, which is intended. If I remove the problematic GeometryReader, then the text is properly rendered at the bottom of the screen, but obviously the frame is not set to the entire width of the screen. Why is this happening?
By default SwiftUI containers tight to content, but GeometryReader consumes maximum of available space. So if to remove second GeometryReader the VStack just wraps internal Text.
If it is still needed to keep second GeometryReader (to read width) and put text to the bottom, the simplest approach would be to add Spacer as below
Group {
GeometryReader { geometry in
VStack {
Spacer()
Text("I want this at the bottom")
}
.frame(width: geometry.size.width, height: nil, alignment: .topLeading)
}
}
Alternate approach of how to stick view at bottom you can find in my answer in this post Position view bottom without using a spacer
How about this?
struct ContentView: View {
var body: some View {
ZStack(alignment: .bottom) {
GeometryReader {geometry in
Text("Centered")
.frame(width: geometry.size.width, height: geometry.size.height)
.background(Color.red)
}
WidthReader {w in
Text("I want this at the bottom").frame(width: w)
}
}
}
}
struct WidthReader<Content: View>: View {
let widthContent: (CGFloat) -> Content
#State private var width: CGFloat = 0
#State private var height: CGFloat = 0
var body: some View {
GeometryReader {g in
widthContent(width).background(
GeometryReader {g1 in
Spacer().onAppear {height = g1.size.height}.onChange(of: g1.size.height, perform: {height = $0})
}
).onAppear {width = g.size.width}.onChange(of: g.size.width, perform: {width = $0})
}.frame(height: height)
}
}
The easiest way is to add the .fixedSize() modifier to your Stack.
I'm playing with SwiftUI animations. I wanted to make a square bigger modifying its frame with an animation this way:
struct ContentView: View {
let squareWidth = CGFloat(100)
let squareHeight = CGFloat(100)
#State private var bigger = false
var body: some View {
VStack {
Color.green
.frame(width: bigger ? self.squareWidth * 2 : self.squareWidth, height: self.squareHeight)
.animation(.default)
Button(action: {
self.bigger.toggle()
}) {
Text("Click me!")
}
}
}
}
The animation happens from the centre of the View, that is the anchor point (0.5, 0.5). Is there a way to change this anchor point? I found out that I can use scaleEffect on the view specifying an anchor:
struct ContentView: View {
let squareWidth = CGFloat(100)
let squareHeight = CGFloat(100)
#State private var bigger = false
var body: some View {
VStack {
Color.green
.frame(width: self.squareWidth, height: self.squareHeight)
.scaleEffect(x: bigger ? 2 : 1, y: 1, anchor: .leading)
.animation(.default)
Button(action: {
self.bigger.toggle()
}) {
Text("Click me!")
}
}
}
}
But it doesn't seem exactly the same. If I needed to apply several effects I should specify the anchor point for each of them, instead of specifying the anchor point on the view just one time. On UIKit I could write:
var myView = UIView(frame: CGRect(x: 0, y: 0, width: 100, height: 100))
myView.layer.anchorPoint = CGPoint(x: 0, y: 0.5) //that is, the .leading of SwiftUI
It is possible, but your approach must change.
There is an implied, built-in centring of views in SwiftUI, which can be confusing.
The parent view is the one placing the content in the centre, recalculating the position of its subviews each time their frames change.
Embedding the VStack in a HStack and adding borders to both clearly shows this behaviour.
Also, note the Spacer(), which pushes the view to the left.
HStack {
VStack {
Color.green
.frame(width: bigger ? self.squareWidth * 2 : self.squareWidth)
.frame(height: self.squareHeight)
.animation(.default)
Button("Click me!") { self.bigger.toggle() }
}.border(Color.black)
Spacer()
}.border(Color.red)
Using the .layer.anchorPoint in UIKit should have the same effect as using scaleEffect/offset/rotationEffect in SwiftUI, as it should affect the origin of the underlying geometry/texture which is being rendered by the GPU.
If you have a complex view (composed of multiple subviews) that needs the same scale effect, you can group them using a Group { } and use .scaleEffect on it.