SwiftUI: Offset Popover Anchor by Constant Amount - swiftui

NOTE: This is a macOS question, not iOS.
I would like to anchor a SwiftUI Popover exactly 70 points to the right of the bottom-leading corner of the "20 Hours Ago" button in this image:
I do NOT want to anchor the popover a certain percentage of the button's width (which would be using UnitPoint). The button's text is updated as the user selects different items in the popover and as the button's width changes, the percentage-based anchor point moves, causing the popover to jump around horizontally. No good. I also don't want to anchor the popover exactly at the bottom-leading corner, as that looks dumb.
The trouble is, I can't figure out what to feed SwiftUI to get a constant, simple horizontal offset for the anchor point of the popover. I'm here:
.popover(isPresented: $isShowingPopover, attachmentAnchor: .point(.bottomLeading), arrowEdge: .bottom, content: {
...
})
How do I specify: "start at the bottom-leading point and then go 70 points to the right"? (Based on the strings that will appear in the button, I KNOW 70 is a good-looking anchor point for all cases.)

You can use .rect:
That defines absolute offsets to the parent content rect, but then you'll have to specify y offset as well.
.popover(isPresented: $show1,
attachmentAnchor: .rect(.rect(CGRect(x: 70, y: 15, width: 0, height: 0))),
arrowEdge: .bottom)
{
Text("Popover")
.frame(width: 200, height: 100)
}

Related

How to display same background color when sheet is opened?

I have a page which has a dark blue background color. When a button is pressed, a sheet is opened, however behind the sheet the blue background shifts down and it shows a white background. How can I prevent this?
ZStack {
setBackgroundColor.darkBlue
.ignoresSafeArea(.all)
HStack {
Button(action: {
self.showSheet = true
}) {
Text("Add exercise")
.frame(maxWidth: .infinity)
.frame(height: 10)
.foregroundColor(CustomColor.darkBlue)
.padding()
.background(CustomColor.cyan)
}
.clipShape(Capsule())
.sheet(isPresented: $showSheet) {
AddWorkoutSheet()
}
}
.padding(.bottom)
}
Blue background shifted when sheet is opened.
I have tried to find out what is wrong, but I cannot seem to figure it out. I quite new to programming and SwiftUI.
It is not shifting down, it is just how sheet works. When you toggle a sheet, it will pop out from the bottom with a default white background, therefore covering the background of your root view. So what you want might be a transparent sheet. If this is the case, using sheet may not be the best way as implementing a transparent sheet needs some walkaround. You may instead try using a transition to move the view up or down with offset.

SwiftUI Remove Extra Space between NavigationTitle and TextView

Started learning SwiftUI, I want to remove this highlighted space between navigation title and Text inside VStack.
If you were to remove the NavigationView and had your VStack as the top-level view in your body, you'd see much the same layout.
That's because its frame is smaller than the frame available for it (in this case, most of the screen) and by default the smaller internal view gets centered both horizontally and vertically.
To move the text to the top of the screen, you need to ensure that your view will grow in size. The easiest way will be add to add a Spacer to the bottom of the VStack:
VStack(alignment: ...) {
Text("...")
Text("...")
Spacer()
}
Another approach would be to wrap your VStack in a scroll view. This view will automatically expand itself to fill the height, and layout its subviews (your VStack) from the top downwards:
ScrollView {
VStack(alignment: ...) {
Text("...")
Text("...")
}
}
Each approach has upsides and downsides depending on how much other data you expect to also be in the view.
You could also manually adjust the frame of your VStack using the .frame() modifier:
VStack(alignment: ...) {
Text("...")
Text("...")
}
.frame(maxHeight: .infinity, alignment: .top)
This would give you much the same effect as using the spacer. But the spacer version is a better way to go, especially if you're only starting out with SwiftUI layout.

Remove Outline on Right-clicked Rows in SwiftUI Mac App List

I'm building a macOS app with SwiftUI, and I'm trying to remove (or even cover up) the border added to a List item when I right-click it.
Here it is by default:
Now with a right-click and a contextMenu view modifier:
I figured this is an NSTableView quirk, so I tried the approaches in these three Stack Overflow posts:
Customize right click highlight on view-based NSTableView
NSTableView with menu, how to change the border color with right click?
Disabling the NSTableView row focus ring
NSTableView: blue outline on right-clicked rows
I couldn't get any of those to work, and that may be due to the fact that I can't subclass an NSTableView, but can only override its properties and methods with an extension. Here's what I have so far that successfully removes the default table background and such:
extension NSTableView{
open override func viewDidMoveToWindow() {
super.viewDidMoveToWindow()
//Remove default table styles
backgroundColor = NSColor.clear
enclosingScrollView!.drawsBackground = false
selectionHighlightStyle = .none
}
}
Is there any way to remove that right-click border in SwiftUI? I'm even open to covering it with other views, but I can't seem to draw SwiftUI views in that space around the table cell.
I found a workaround for this. I put my List in a ZStack and then set its opacity to zero. I then built out a fully custom version of the same list, but using LazyVStack:
//Message List
ZStack{
//Ghost list for keyboard control
List($model.messages, id: \.self, selection: $model.selectedMessages){ $message in
MessageItemView(message: $message)
}
.focusable()
.opacity(0)
//Custom UI for the above List
ScrollView{
ZStack{
LazyVStack(spacing: 5){
ForEach($model.messagesToday){ $message in
MessageItemView(message: $message)
}
}
}
.frame(maxWidth: .infinity, maxHeight: .infinity)
}
}
.frame(maxWidth: .infinity, maxHeight: .infinity)
Each list is bound to the same model, so if I click a message to select it in the custom UI, the same thing gets selected in the invisible List. All the keyboard shortcuts that come with table use in a List look like they are working on the custom version.
So how does this solve my original problem? You can right-click on the custom MessageItemView and the default ring around the cell is invisible, but the contextMenu still works (it's defined inside my MessageItemView).
This isn't as elegant as I'd like, but it's nice to have 100% control over the UI but still get all the keyboard controls that come for free with a List.

SwiftUI - ScrollViewReader scroll to top including padding (anchor plus constant)

I have a ScrollViewReader and the scrollTo method works.
proxy.scrollTo(target, anchor: .top)
The item which is scrolled to is completely aligned with the .top-Anchor.
Now I want to include the padding of 7px.
Is there a SwiftUI equivalent to the NSLayoutAnchor below or any other method to make the green overlay always align with the List-Element.
NSLayoutAnchor(equalTo:self.view.topAnchor constant:7)

SwiftUI: Different Widths for Multiline Text Views in Boxes

Here is how two boxes with text are rendering in SwiftUI, but I want them to both be full-width:
Here is my code:
VStack(spacing: 20){
//Help ---
VStack(alignment: .leading, spacing:10){
Text("Help").h2()
Text("Please view our online user guide before contacting us since we answer most questions there.")
.lineLimit(nil) //Make the text wrap
.prefsText()
Button("Online Help"){
openURL(URL(string: "https://avid.pro/help")!)
}.foregroundColor(Color(accentColor))
.buttonStyle(PlainButtonStyle())
.prefsLink()
}.unoBoxRoundPad()
.frame(maxWidth: .infinity)
//Support ---
VStack(alignment: .leading, spacing:10){
Text("Support").h2()
Text("Email us if you need to submit a bug or get specialized help. It may take us a few days to get back to you.")
.lineLimit(nil) //Make the text wrap
.prefsText()
Button("Email Us"){
openURL(URL(string: "mailto:help#avid.pro")!)
}.foregroundColor(Color(accentColor))
.buttonStyle(PlainButtonStyle())
.prefsLink()
}.unoBoxRoundPad()
.frame(maxWidth: .infinity)
}.background(Color.yellow) //For testing
I can't for the life of me figure out why they aren't the same width. .unoBoxRoundPad() is a view modifier that adds these shared styles:
.padding(20)
.background(Color("UnoDark"))
.cornerRadius(7)
If I put .frame(maxWidth: .infinity) on the Text() instead of on the containing VStack, it seems to get a little closer, but then it disregards the containing view's padding():
Any ideas on what I'm doing wrong?
There are two issues at play here:
You've got two modifiers swapped (.frame(...) and .unoBoxRoundPad()); you want the roundedness to apply to the entire stretched thing. By putting .unoBoxRoundPad() first, you're saying "pad this thing" and then "place that rounded thing inside an infinitely-wide box"; you want the reverse: your thing should be placed inside an infinitely-wide box, and it's the infinitely wide box that should have the rounded corners and padding.
You need to specify an alignment: when using that .frame() modifier; when the inner view is placed inside an infinitely-wide box, it's going to default to being centered vertically and horizontally inside it. Based on your screenshots, you probably want to use .topLeading so that the content ("Support", "Help", etc) start in the top-left corner (or top-right in RTL languages).