Can Not Get Correct Position of View in Swiftui

Can not get correct position of view in SwiftUI

It seems to work just fine with .global coordinates. See my demo below. Maybe there is a problem with the use of xAxis in TabBarShape ... can't tell because you didn't share it.

Sample Image

import SwiftUI

enum Tab: CaseIterable {
case home
case search
case media
}

struct ContentView: View {
@State var selectedTab: Tab = .home
@State private var xAxis: CGFloat = 0

private let home = Text("home")
private let search = Text("search")
private let media = Text("media")

var body: some View {
ZStack(alignment: .center) {
TabView(selection: $selectedTab) {
switch selectedTab {
case .home:
home
.ignoresSafeArea()
case .search:
search
.ignoresSafeArea()
case .media:
media
.ignoresSafeArea()
}
}
VStack {
Spacer()
HStack(spacing: 0) {
ForEach(Tab.allCases, id: \.self) { tab in
if tab == .search { Spacer(minLength: 0) }

GeometryReader { proxy in
Button {
withAnimation {
selectedTab = tab
xAxis = proxy.frame(in: .global).midX // .global should work fine
}
} label: {
Image(systemName: "house")
.resizable()
.renderingMode(.template)
.aspectRatio(contentMode: .fit)
.frame(width: 25, height: 25)
.foregroundColor(tab == selectedTab ? Color.blue : Color.gray)
}
.onAppear {
if tab == selectedTab {
xAxis = proxy.frame(in: .global).midX // set for initial selection
}
}
}
.frame(width: 25, height: 25)

if tab == .search { Spacer(minLength: 0) }
}
}
.padding(.horizontal, 60)
.padding(.vertical)
.background(
ZStack {
Color.green
Circle().fill(.white).frame(width: 50)
.position(x: xAxis) // applied here
}
)
.cornerRadius(50)
// .padding(.horizontal, idiomIsPhone() ? nil : tabBarHorizontalPaddingForIpad())
Spacer()
.frame(height: 5)
}
}
}
}

How to get the position of a view in SwiftUI

Complete Working Code:

struct PositioningViews: View {
@State private var xPosition2:CGFloat = 0
@State private var yPosition2:CGFloat = 0
@State private var xPosition3:CGFloat = 0
@State private var yPosition3:CGFloat = 0
@State private var offsetValueX = 0.0
@State private var offsetValueY = 0.0
var body: some View {
HStack {
Text("View 1")
.background(.purple)


Text("View 2")
.background(.orange)
.overlay(
GeometryReader{ geo -> AnyView in

AnyView(Color.clear
.onAppear(){

xPosition2 = geo.frame(in: .global).midX
yPosition2 = geo.frame(in: .global).midY


})
}
)

.background(.blue)
.offset(x: offsetValueX, y: offsetValueY)
.onAppear(){
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
withAnimation {
offsetValueX = xPosition3 - xPosition2
offsetValueY = yPosition3 - yPosition2
}
}
}
.zIndex(2)


Text("View 3")
.background(.blue)
.overlay(
GeometryReader{ geo -> AnyView in

AnyView(Color.clear
.onAppear(){

xPosition3 = geo.frame(in: .global).midX
yPosition3 = geo.frame(in: .global).midY


})
}
)
.zIndex(1)
}
}
}

EDIT, Without AnyView:

struct PositioningViews: View {
@State private var xPosition2:CGFloat = 0
@State private var yPosition2:CGFloat = 0
@State private var xPosition3:CGFloat = 0
@State private var yPosition3:CGFloat = 0
@State private var offsetValueX = 0.0
@State private var offsetValueY = 0.0
var body: some View {
HStack {
Text("View 1")
.background(.purple)


Text("View 2")
.background(.orange)
.overlay(
GeometryReader{ geo in
Color.clear
.onAppear(){
xPosition2 = geo.frame(in: .global).midX
yPosition2 = geo.frame(in: .global).midY
}
}
)
.background(.blue)
.offset(x: offsetValueX, y: offsetValueY)
.onAppear(){
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
withAnimation {
offsetValueX = xPosition3 - xPosition2
offsetValueY = yPosition3 - yPosition2
}
}
}
.zIndex(2)

Text("View 3")
.background(.blue)
.overlay(
GeometryReader{ geo in
Color.clear
.onAppear(){
xPosition3 = geo.frame(in: .global).midX
yPosition3 = geo.frame(in: .global).midY

}
}
)
.zIndex(1)
}
}
}

Get coordinates in real time of a moving view in SwiftUI

You can't access realtime animation value in SwiftUI.

Instead you can animate it by yourself, by calculating position for each frame. CADisplayLink will help you with that: it's a Timer analogue, but is called on each frame render, so you can update your value.

struct Page: View {
@ObservedObject var P: Position = Position()
var body: some View {
VStack {
Point()
.position(x: P.realtimePosition.x, y: P.realtimePosition.y)
Text("X: \(P.realtimePosition.x), Y: \(P.realtimePosition.y)")
}.onAppear() {
P.startMovement()
}
}
}

class Position: ObservableObject {
struct AnimationInfo {
let startDate: Date
let duration: TimeInterval
let startPoint: CGPoint
let endPoint: CGPoint

func point(at date: Date) -> (point: CGPoint, finished: Bool) {
let progress = CGFloat(max(0, min(1, date.timeIntervalSince(startDate) / duration)))
return (
point: CGPoint(
x: startPoint.x + (endPoint.x - startPoint.x) * progress,
y: startPoint.y + (endPoint.y - startPoint.y) * progress
),
finished: progress == 1
)
}
}

@Published var realtimePosition = CGPoint.zero

private var mainTimer: Timer = Timer()
private var executedTimes: Int = 0
private lazy var displayLink: CADisplayLink = {
let displayLink = CADisplayLink(target: self, selector: #selector(displayLinkAction))
displayLink.add(to: .main, forMode: .default)
return displayLink
}()
private let animationDuration: TimeInterval = 0.1
private var animationInfo: AnimationInfo?

private var coordinatesPoints: [CGPoint] {
let screenWidth = UIScreen.main.bounds.width
let screenHeight = UIScreen.main.bounds.height
return [CGPoint(x: screenWidth / 24 * 12, y: screenHeight / 24 * 12),
CGPoint(x: screenWidth / 24 * 7, y: screenHeight / 24 * 7),
CGPoint(x: screenWidth / 24 * 7, y: screenHeight / 24 * 17)
]
}

func startMovement() {
mainTimer = Timer.scheduledTimer(timeInterval: 2.5,
target: self,
selector: #selector(movePoint),
userInfo: nil,
repeats: true)
}

@objc func movePoint() {
if (executedTimes == coordinatesPoints.count) {
mainTimer.invalidate()
return
}
animationInfo = AnimationInfo(
startDate: Date(),
duration: animationDuration,
startPoint: realtimePosition,
endPoint: coordinatesPoints[executedTimes]
)
displayLink.isPaused = false
executedTimes += 1
}

@objc func displayLinkAction() {
guard
let (point, finished) = animationInfo?.point(at: Date())
else {
displayLink.isPaused = true
return
}
realtimePosition = point
if finished {
displayLink.isPaused = true
animationInfo = nil
}
}
}

can I get the position of a `View` after layout in SwiftUI?

Use a GeometryReader to get the frame of each view and use the frame to determine the points for a path between the two views.

struct ContentView : View {
var body: some View {
GeometryReader { geometry -> Text in
let frame = geometry.frame(in: CoordinateSpace.local)
return Text("\(frame.origin.x), \(frame.origin.y), \(frame.size.width), \(frame.size.height)")
}
}
}

How to get location of a view- swiftUI

Wrap your view with a GeometryReader:

struct ContentView: View {

var body: some View {

GeometryReader { geo in

// frame of YourView in global coordinates
// gives you minX, minY, widht, height and more
_ = geo.frame(in: .global)

YourView()
.gesture(DragGesture()
.onChanged({ value in
_ = value.translation.width // relative drag x
_ = value.translation.height // relative drag y
})
)
}
}
}

SwiftUI Get Coordinates of TextField or any View

Here is a demo of how specific view coordinates can be read (see helpful comments inline)

struct DemoReadViewOrigin: View {

@State private var someNumber1 = "1000"
@State private var someNumber2 = "2000"
//bunch more

@State private var enteredNumber = "Some Text at the Top"
@State var value: CGFloat = 0

var body: some View {
ScrollView {
VStack {
Spacer()
Text("\(enteredNumber)")
Spacer()

Group { //1
TextField("Placeholder", text: $someNumber1)
.keyboardType(.default)
.textFieldStyle(RoundedBorderTextFieldStyle())
.background(GeometryReader { gp -> Color in
let rect = gp.frame(in: .named("OuterV")) // < in specific container
// let rect = gp.frame(in: .global) // < in window
// let rect = gp.frame(in: .local) // < own bounds
print("Origin: \(rect.origin)")
return Color.clear
})

//this does not work
.onTapGesture {
}

TextField("Placeholder", text: $someNumber2)
.keyboardType(.default)
.textFieldStyle(RoundedBorderTextFieldStyle())
}//group 1

//bunch more

Button(action: {
self.enteredNumber = self.someNumber1
self.someNumber1 = ""
// UIApplication.shared.endEditing()
}) {
Text("Submit")
}
.padding(.bottom, 50)

}//outer v
.coordinateSpace(name: "OuterV") // << declare coord space
.padding(.horizontal, 16)
.padding(.top, 44)

}//Scrollview or Form
// .modifier(AdaptsToSoftwareKeyboard())
}
}

How To Position Views Relative To Their Top Left Corner In SwiftUI

@Asperi 's answer will solve the problem. But, I think we should use Spacer() rather than Color.clear and ZStack.

Spacer is specifically designed for these scenarios and makes the code easier to understand.

struct HomeView: View {
var body: some View {
HStack {
VStack(alignment: .leading) {
Text("Top Text")
.font(.system(size: 20))
.fontWeight(.medium)

Text("Bottom Text")
.font(.system(size: 12))
.fontWeight(.regular)
Spacer()
}
Spacer()
}
}
}

SwiftUI layout system is different from UIKit.

It asks each child view to calculate its own size based on the bounds of its parent view. Next, asks each parent to position its children within its own bounds.

https://www.hackingwithswift.com/books/ios-swiftui/how-layout-works-in-swiftui

SwiftUI set position to center of different view

Here is possible approach (with a bit simplified your initial snapshot and added some convenient View extension).

Tested with Xcode 11.2 / iOS 13.2

Sample Image

extension View {
func rectReader(_ binding: Binding<CGRect>, in space: CoordinateSpace) -> some View {
self.background(GeometryReader { (geometry) -> AnyView in
let rect = geometry.frame(in: space)
DispatchQueue.main.async {
binding.wrappedValue = rect
}
return AnyView(Rectangle().fill(Color.clear))
})
}
}

struct ContentView: View {

@State private var tap = false
@State private var bottomRect: CGRect = .zero

var body: some View {
ZStack(alignment: .bottom) {
RoundedRectangle(cornerRadius: 10)
.frame(maxWidth: .infinity, maxHeight: 50, alignment: .center)
.padding()
.rectReader($bottomRect, in: .named("board"))

Rectangle()
.foregroundColor(.red)
.overlay(Text("Click me")
.fontWeight(.light)
.foregroundColor(.white)
)
.frame(width: 50, height: 50)
.position(x: self.tap ? bottomRect.midX : 50,
y: self.tap ? bottomRect.midY : 50)
.onTapGesture {
withAnimation {
self.tap.toggle()
}
}
}.coordinateSpace(name: "board")
}
}


Related Topics



Leave a reply



Submit