Center SwiftUI view in top-level view
Here is a demo of possible approach. The idea is to use injected UIView to access UIWindow and then show loading view as a top view of window's root viewcontroller view.
Tested with Xcode 12 / iOS 14 (but SwiftUI 1.0 compatible)
Note: animations, effects, etc. are possible but are out scope for simplicity
struct CenteredLoadingView<RootView: View>: View {
private let rootView: RootView
@Binding var isActive: Bool
init(rootView: RootView, isActive: Binding<Bool>) {
self.rootView = rootView
self._isActive = isActive
}
var body: some View {
rootView
.background(Activator(showLoading: $isActive))
}
struct Activator: UIViewRepresentable {
@Binding var showLoading: Bool
@State private var myWindow: UIWindow? = nil
func makeUIView(context: Context) -> UIView {
let view = UIView()
DispatchQueue.main.async {
self.myWindow = view.window
}
return view
}
func updateUIView(_ uiView: UIView, context: Context) {
guard let holder = myWindow?.rootViewController?.view else { return }
if showLoading && context.coordinator.controller == nil {
context.coordinator.controller = UIHostingController(rootView: loadingView)
let view = context.coordinator.controller!.view
view?.backgroundColor = UIColor.black.withAlphaComponent(0.8)
view?.translatesAutoresizingMaskIntoConstraints = false
holder.addSubview(view!)
holder.isUserInteractionEnabled = false
view?.leadingAnchor.constraint(equalTo: holder.leadingAnchor).isActive = true
view?.trailingAnchor.constraint(equalTo: holder.trailingAnchor).isActive = true
view?.topAnchor.constraint(equalTo: holder.topAnchor).isActive = true
view?.bottomAnchor.constraint(equalTo: holder.bottomAnchor).isActive = true
} else if !showLoading {
context.coordinator.controller?.view.removeFromSuperview()
context.coordinator.controller = nil
holder.isUserInteractionEnabled = true
}
}
func makeCoordinator() -> Coordinator {
Coordinator()
}
class Coordinator {
var controller: UIViewController? = nil
}
private var loadingView: some View {
VStack {
Color.white
.frame(width: 48, height: 72)
Text("Loading")
.foregroundColor(.white)
}
.frame(width: 142, height: 142)
.background(Color.primary.opacity(0.7))
.cornerRadius(10)
}
}
}
struct CenterView: View {
@State private var isLoading = false
var body: some View {
return VStack {
Color.gray
HStack {
CenteredLoadingView(rootView: list, isActive: $isLoading)
otherList
}
Button("Demo", action: load)
}
.onAppear(perform: load)
}
func load() {
self.isLoading = true
DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
self.isLoading = false
}
}
var list: some View {
List {
ForEach(1..<6) {
Text($0.description)
}
}
}
var otherList: some View {
List {
ForEach(6..<11) {
Text($0.description)
}
}
}
}
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
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")
}
}
SwiftUI: Align a View to the center of its Parent
Here is one case with Zstack:
struct ListMissionView : View{
var missions: [Mission] = [
Mission(name: "Cassini",
launchDate: "10/15/97",
launchLocation: "Cape Canaveral",
missionEndDate: "09/15/17",
status: "Past"),
Mission(name: "Galaxy Evolution Explorer",
launchDate: "04/28/03",
launchLocation: "Cape Canaveral",
missionEndDate: "06/28/13",
status: "Past"),
Mission(name: "IRAS",
launchDate: "01/25/83",
launchLocation: "Vandenberg",
missionEndDate: "11/21/83",
status: nil),
Mission(name: "NuSTAR",
launchDate: "06/13/12",
launchLocation: "Central Pacific Ocean",
missionEndDate: nil,
status: "Current"),
Mission(name: "Voyager 1",
launchDate: "09/05/77",
launchLocation: "Central Pacific Ocean",
missionEndDate: nil,
status: nil)
]
var body: some View {
GeometryReader{ proxy in
List {
ForEach(self.missions, id: \.name) { mission in
MissionCell(mission: mission, proxy: proxy)
}
}
}
}
}
struct MissionCell: View {
var mission: Mission
var proxy: GeometryProxy
var body: some View {
// VStack(alignment: .missionOptionalInfoAlignment, spacing: 2) {
VStack(alignment: .leading, spacing: 2) {
Text(self.mission.name)
.lineLimit(1)
.truncationMode(.middle)
ZStack(alignment: .topLeading) {
VStack(alignment: .leading) {
HStack {
Image(systemName: "calendar")
.foregroundColor(Color(UIColor.systemGray))
Text(self.mission.launchDate)
.foregroundColor(Color(UIColor.systemGray))
}
HStack {
Image(systemName: "location.fill")
.foregroundColor(Color(UIColor.systemGray))
Text(self.mission.launchLocation)
.foregroundColor(Color(UIColor.systemGray))
}
}
VStack(alignment: .leading) {
if self.mission.missionEndDate != nil {
HStack {
Image(systemName: "hand.raised.slash.fill")
.foregroundColor(Color(UIColor.systemGray))
Text(self.mission.missionEndDate!)
.foregroundColor(Color(UIColor.systemGray))
}
}
if self.mission.status != nil {
HStack {
Image(systemName: "checkmark")
.foregroundColor(Color(UIColor.systemGray))
Text(self.mission.status ?? "")
.foregroundColor(Color(UIColor.systemGray))
}
}
}.alignmentGuide(.leading){ _ in return -self.proxy.size.width / 2.0}
}
}
.padding([.leading, .trailing], 16)
}
}
Top alignment/position of text in SwiftUI
You can use a Spacer()
after the Text View to push it upwards.
struct ContentView: View {
var body: some View {
VStack {
Text("Hello World!")
Spacer()
}
}
}
SwiftUI view is in the middle instead of in the top
I had the same issue, but found the the problem I had was in using navigationView multiple times.
I thought that we should have NavigationView in every view, but apparently, we should place navigationView only in the main view of the application, and all other views that we enter via navigationLink, automatically get the back option without the need to mention NavigationView again.
So check if you use navigationView more than once in your code.
Strangely, we can specify navigationBarTitle even in views that dont have the navigationView mentioned in them, this is because the navigationView is at the parent view.
Use "displayMode: .inline", to make the navigation area as minimal as possible, it will save you real-estate.
Another thing to do, is to use Spacer().
If you want all your items to be at the top, just put Spacer() as the last item in VStack, and it will push all items to the top.
See this example:
VStack {
// What you care about displaying
Text("Something to Show 1")
Text("Something to Show 2")
Text("Something to Show 3")
// This should be the last, put everything to the top
Spacer()
}
.navigationBarTitle(Text("The Title"), displayMode: .inline)
Related Topics
Start Position of Table View Is Wrong in Simulator, and Is Not The Same in Storyboard
How to Programmatically Wrap Png Texture Around Cube in Scenekit
Implementing Codable for UIcolor
How to Call Objective-C Instancetype Method in Swift
Detect Array with Issue (Debug Mode)
Multiple Async Requests at Once in Objective C
Conforming to UItableviewdelegate and UItableviewdatasource in Swift
Disabling Allowsbackgroundlocationupdates (Cllocationmanager) Doesn't Work After Is Was Enabled
Trim End Off of String in Swift, Getting Error at Runtime
Contentview with UItableview Not Scrolling Within UIscrollview
Bug When Trying to Parse Date in Objective-C
Sync Video in Avplayerlayer and Avplayerviewcontroller
How to Print Out(Nslog) The Properties of a Custom Object Added to a Nsmutablearray
Spritekit Not Respecting Zposition
How to Mock Network Requests in Xcode UI Tests While The Tests Are Running