Using SwiftUI on watchOS, how to align views to the navigation title? - swift

I would like to align some content to the same inset the the navigation title uses.
On older watch devices, there is no inset, but on newer devices with the rounded corners, the title is inset, and for certain things I would like to align to the same inset.
The apple docs talk about this margin, but I can't figure out how to use it.
How can I modify this example to have the left edge of the Text and Button align with the Navigation Title?
var body: some View {
VStack(alignment: .leading) {
Text("Hello World")
Button("Test") {}
}
.navigationTitle("Title")
}

Use .scenePadding(.horizontal). That will automatically apply the padding you want on the larger watches. Docs: https://developer.apple.com/documentation/swiftui/menu/scenepadding(_:)

Leading padding is simply (on the VStack):
.padding(.leading, 9.5)
but you have to do it based on the watch model.
Here is an example of how to determine the watch model:
How to determine the Apple Watch model?
And you might want to put this in a conditional view modifier:
https://stackoverflow.com/a/62962375/14351818

You could use the current screen width:
var screenWidth = WKInterfaceDevice.current().screenBounds.width
and apply that (minus the desired margin) as a frame width to your VStack:
VStack(alignment: .leading){
Text("Hello World")
Button("Test") {}
}
.frame(width: screenWidth - 25)
.navigationTitle("Title")
This worked fine on 45mm and 40mm, but unfortunately still not quite there on the 38mm watch, so you might want to explicitly set different paddings/frames for smaller devices.

Related

Why does SwiftUI View background extend into safe area?

Here's a view that navigates to a 2nd view:
struct ContentView: View {
var body: some View {
NavigationView {
NavigationLink {
SecondView()
} label: {
Text("Go to 2nd view")
}
}
}
}
struct SecondView: View {
var body: some View {
ZStack {
Color.red
VStack {
Text("This is a test")
.background(.green)
//Spacer() // <--If you add this, it pushes the Text to the top, but its background no longer respects the safe area--why is this?
}
}
}
}
On the 2nd screen, the green background of the Text view only extends to its border, which makes sense because the Text is a pull-in view:
Now, if you add Spacer() after the Text(), the Text view pushes to the top of the VStack, which makes sense, but why does its green background suddenly push into the safe area?
In the Apple documentation here, it says:
By default, SwiftUI sizes and positions views to avoid system defined
safe areas to ensure that system content or the edges of the device
won’t obstruct your views. If your design calls for the background to
extend to the screen edges, use the ignoresSafeArea(_:edges:) modifier
to override the default.
However, in this case, I'm not using ignoresSafeArea, so why is it acting as if I did, rather than perform the default like Apple says, which is to avoid the safe areas?
You think that you use old background modifier version.
But actually, the above code uses a new one, introduced in iOS 15 version of background modifier which by default ignores all safe area edges:
func background<S>(_ style: S, ignoresSafeAreaEdges edges: Edge.Set = .all) -> some View where S : ShapeStyle
To fix the issue just replace .background(.green) with .background(Color.green) if your app deployment target < iOS 15.0, otherwise use a new modifier: .background { Color.green }.

Round Specific Corners of Stroke/Border SwiftUI

I am working on a SwiftUI project where I need a view to have a border with only some of the corners rounded (for instance, the top left and top right).
I added a RoundedRectangle with a stroke and was able to have all of the corners rounded. However, I need only some of the corners to be rounded and I couldn't figure out a way to do that.
This is the code I had to add a RoundedRectangle:
.overlay(
RoundedRectangle(cornerRadius: 20)
.stroke(Color.gray, lineWidth: 1)
)
To make only specific corners rounded, I looked at this Stackoverflow post: Round Specific Corners SwiftUI. However, I would have to get rid of the Rounded Rectangle (because it rounds all corners). I would have to use a normal border instead. But, with a normal border, it will cut out a piece of the border when rounding corners and trying any of the answers provided.
This is what I would ideally want it to look like (this is from an example from Bootstrap - we are rebuilding a website as an app):
Thank you!
Here's an alternative / easier way to recreate this. Add a white background to each of the rows and add a single gray background color behind them. Add spacing between the rows to let the gray background color appear like a divider between them. Then just add a rectangle overlay to the entire view, like you already had in your code!
struct CornerView: View {
var body: some View {
VStack(spacing: 1) {
ForEach(0..<5) { index in
Text("Item \(index)")
.frame(maxWidth: .infinity)
.frame(height: 55)
.background(Color.white)
}
}
.background(Color.gray)
.overlay(
RoundedRectangle(cornerRadius: 5)
.stroke(Color.gray, lineWidth: 1)
)
.padding()
}
}
struct CornerView_Previews: PreviewProvider {
static var previews: some View {
CornerView2()
}
}

Forcing a View to the bottom right of View in SwiftUI

I'm teaching myself SwiftUI by taking the 2020 version of Stanford's CS193p posted on YouTube.
I'd like to have a ZStack with a check box in the bottom right. This code puts the check mark
right in the middle of the View.
ZStack {
.... stuff ....
Text("✓")
}
Much to my surprise, this code puts the check mark in the top left corner of the View. (My mental model of GeometryReader is clearly wrong.)
ZStack {
.... stuff ...
GeometryReader { _ in Text("✓") }
}
I can't use ZStack(alignment: .bottomTrailing) { ... } as suggested in a different StackOverflow answer, as I want the alignment to apply only to the text, and not to anything else.
Is there any way to get the check mark on the bottom right? I'm trying to avoid absolute offsets, since I want my code to work no matter what the size of the ZStack view. Also, is there a good tutorial on layout so that I can figure out the answer to these questions myself?
[In case you're wondering. I'm playing around with Homework Assignment 3. I'm adding a "cheat mode" where if there is a matchingSet on the board, it marks them with a small check mark.]
Try applying
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .bottomTrailing)
to Text.
I would say that it's possible if you know the size of the Text and the largest subview in the ZStack.
In the example below I've used 40 as the Text width and 20 as the Text height, and 200 (square) for the largest Color subview. You can then calculate the subview offsets using relative offsets and layout guides.
This is useful if you don't want to expand the Text to fill the container like the above answer.
struct ColorsView: View {
var body: some View {
ZStack(alignment: .center) {
Color.red.frame(width: 200,
height: 200,
alignment: .center)
Text("Bye")
Text("Hello").frame(width: 40, height: 20)
.alignmentGuide(HorizontalAlignment.center, computeValue: { dimension in
-60
})
.alignmentGuide(VerticalAlignment.center, computeValue: { dimension in
-80
})
}
}
}

why geometry reader doesn't center its child?

red border is geometry Area and black border is text area
currently using Xcode12 Beta 3
struct Testing_Geometry_: View {
var body: some View {
GeometryReader { geo in
Text("Hello, World!")
.border(Color.black)
}
.border(Color.red)
}
}
I wanted to position text in center with this code
struct Testing_Geometry_: View {
var body: some View {
GeometryReader { geo in
Text("Hello, World!")
.position(x:geo.frame(in:.global).midX,y:geo.frame(in:.global).midY)
.border(Color.black)
}
.border(Color.red)
}
}
but I got this result which means Text is taking the whole geometry size and I think it's not correct!
cause texts has to fit in their space
three roles suggested by #twostraws for layout systems are
1- parent offers its size
2-child chooses its size
3-parent positions its child
but I think this isn't right!
text is taking the whole geometry space
If someone is looking for basic solution, you can put it in one of Stack and make it use whole size of geometry size with alignment center. That will make all elements underneath to use correct size and aligned in center
GeometryReader { geometry in
ZStack {
// ... some of your views
}
.frame(width: geometry.size.width, height: geometry.size.height, alignment: .center)
}
The problem is that modifier order matters, because modifiers actually create parent views. I've used backgrounds instead of borders because I think they're easier to see. Consider this code that's the same as yours but just using a background:
struct TestingGeometryView: View {
var body: some View {
GeometryReader { geo in
Text("Hello, World!")
.position(x:geo.frame(in:.global).midX,y:geo.frame(in:.global).midY)
.background(Color.gray)
}
.background(Color.red)
}
}
This gives the following:
From this you are thinking "Text is taking the whole geometry size and I think it's not correct!" because the gray background is taking the whole screen instead of just around the Text. Again, the problem is modifier order- the background (or border in your example) is a parent view, but you are making it the parent of the "position" view instead of the Text view. In order for position to do what it does, it takes the entire parent space available (in this case the whole screen minus safe area). So putting background or border as parent of position means they will take the entire screen.
Let's switch the order to this, so that background view is only for the Text view and we can see size of Text view:
struct TestingGeometryView: View {
var body: some View {
GeometryReader { geo in
Text("Hello, World!")
.background(Color.gray)
.position(x:geo.frame(in:.global).midX,y:geo.frame(in:.global).midY)
}
.background(Color.red)
}
}
This gives the result I think you were expecting with the Text view only taking up the minimum size required, and following those rules that #twostraws explained so nicely.
This is why modifier order is so important. It's clear that GeometryReader view is taking up the entire screen, and Text view is only taking up the space it requires. In your example, Text view was still only taking up the required space but your border was around the position view, not the Text view. Hope it's clear :-)

How can I vertically center text-only TabBar buttons in SwiftUI?

I'm making some TabView buttons in SwiftUI (Xcode 11.1, Swift 5.1, and iOS 13.1.3).
For my TabView, I don't want any images -- just text. This code accomplishes that nicely:
import SwiftUI
struct ContentView: View {
var body: some View {
TabView {
Text("The First Tab")
.tabItem {
Text("My Projects")
}
Text("Another Tab")
.tabItem {
Text("Augmented Reality")
}
Text("The Last Tab")
.tabItem {
Text("Products")
}
}
}
}
However, in this case, the text ends up aligned to the very bottom of the tab bar items, like this:
What I want, though is for the tab bar not to reserve space for the icons, and to vertically center the text -- something like this mock-up:
I've tried sticking it in a VStack and trying to adjust the alignment, but nothing changes.
Is there some smart way to do this, or do I need to do some sort of offset by a specific number of points?
Also FYI, Apple's developer doc says, "Tab views only support tab items of type Text, Image, or an image followed by text. Passing any other type of view results in a visible but empty tab item."
I should add that I can use .offset to adjust the entire TabView, but that's obviously not what we want. .tabItem itself ignores any .offset given, as does the Text within .tabItem.
I was able to get closer, by doing this -- essentially I'm moving the content view for each tab down by 40.0 points, and then moving the entire TabView up by 40. This looks much closer, but the background behind the tabs is then messed up:
Here's the code:
struct ContentView: View {
let vOffset: CGFloat = 40.0
var body: some View {
TabView {
Text("The First Tab")
.tabItem {
Text("My Projects")
}.offset(CGSize(width: 0.0, height: vOffset))
Text("Another Tab")
.tabItem {
Text("Augmented Reality")
}.offset(CGSize(width: 0.0, height: vOffset))
Text("The Last Tab")
.tabItem {
Text("Products")
}.offset(CGSize(width: 0.0, height: vOffset))
}
.offset(CGSize(width: 0.0, height: -vOffset))
}
}
Here's what it looks like:
I assume it will be possible in some way to fix that background, though haven't quite figured out how yet.
The other thought is that I wonder if it's even a good idea to do this sort of "hacky" thing. Or if this even is a hacky thing? I know the whole idea of the declarative nature of SwiftUI is to separate the implementation from the declaration. With that in mind, it would be conceivable to expect that some future implementation could look very different, and thus be made to look stupid via the hacky offsets I'm doing here.
That aside, I still want to do it, for now anyway. 😊
So for now I'm looking for a way to fix the background color of the tab bar area, and also, of course, a less hacky way to solve the original problem.
Thanks!
Update: Xcode 13.3 / iOS 15.4
Not Text is entered (that can be considered as fix for PO)...
... but image is always shown to the top of center (independently of there is text or not):
The empty space is room for an icon. The TabView is not really customizable. You can turn the text into graphics and insert it using Image - that should push it upwards. It won’t be perfectly centered though.
try
TabView(alignment: .center ,spacing: 20))
{
Text("The First Tab")
.tabItem {
Text("My Projects")
}
}