I'm unable to show a context menu over a SpriteView in SwiftUI on a mac. I am able to show a context menu not over it -- right click on the blue works, on red doesn't, see image.
However, I also need to be able to pan the scene in the view, so mouseDragged() inside the scene must work. Placing an overlay() on top of the view blocks mouse dragging events from propagating down to the scene.
Xcode 13.2, deployment 12.2, Swift 5
import SwiftUI
import SpriteView
func MenuItem(_ text: String, _ action: #escaping ()->Void) -> some View {
Button {
action()
} label: {
Text(text)
}
}
var scene : SKScene {
let r = SKScene.init(size: CGSize(width: 500, height: 500))
r.isUserInteractionEnabled = false
r.scaleMode = .aspectFill
r.backgroundColor = .red
return r
}
struct ContentView: View {
var body: some View {
ZStack {
SpriteView.init(scene: scene)
.padding()
.contextMenu {
MenuItem("1 Preferences ...") {
}
}
}
.background(Color.blue)
.contextMenu {
MenuItem("0 Preferences ...") {
}
}
}
}
A possible solution is to add transparent overlay and attach context menu there, like
SpriteView.init(scene: scene)
.padding()
.overlay(
Color.clear
.contentShape(Rectangle())
.contextMenu { // << here !!
MenuItem("1 Preferences ...") {
}
}
)
Tested with Xcode 13.4 / macOS 12.5
Related
In my macOS app (SwiftUI project multiplateform), I have a main toolbar that works well.
However, when I loaded a view, in a popover for example, the toolbar items set in the popover view, are added to the main toolbar.
In this image above, the X icon visible at top belongs to the popover view. It's added when loading the popover and hidden when leaving the popover.
The toolbar code in the popover view:
toolbar {
Button(action: {
selectedItemId = nil // set
},
label: { Image(systemName: "x.circle") })
}
Adding a searchable field will also be added in the main toolbar, and can break completly the toolbar layout.
.searchable(text: $searchText, placement: .navigationBarDrawer(displayMode: .always)) {}
How macOS controls the different toolbar items from different views ? Each new view shoulb be a "window" to control a new toolbar attached to it ?
Thanks in advance.
.popover does not support Toolbars.
And in a Sheet the toolbar items are placed at the bottom.
But you can always include your own controls and buttons in a Popover. Just don't use .toolbar:
struct ContentView: View {
#State private var showSheet = false
#State private var showPopover = false
var body: some View {
VStack {
Button("Show sheet") { showSheet = true }
Button("Show popover") { showPopover = true}
}
.toolbar {
Button {
} label: {
Image(systemName: "house")
}
}
.sheet(isPresented: $showSheet) {
VStack {
Text("Dummy Sheet")
List(0..<20) { i in
Text("item \(i)")
}
.toolbar {
Button {
} label: {
Image(systemName: "x.circle")
}
}
}
.padding()
.frame(minWidth: 200, minHeight: 200)
}
.popover(isPresented: $showPopover) {
VStack {
Text("Dummy Popover")
List(0..<20) { i in
Text("item \(i)")
}
Divider()
Button {
} label: {
Image(systemName: "x.circle")
}
}
.frame(minWidth: 200, minHeight: 200)
.padding(.vertical)
}
}
}
I'm making a macOS app in SwiftUI with the new NavigationSplitView. If a user resizes the sidebar, I'd like that new width to be remembered and restored when the app next loads.
A preference can be read in like so...
#State private var width = UserDefaults.standard.float(forKey: "sidebarWidth")
...
NavigationSplitView {
...
}.navigationSplitViewColumnWidth(ideal: width)
But this isn't a binding, so the width isn't updated when it changes.
Is it possible to save the current sidebar width when it changes (or when the app closes), so that it might later be restored?
Many thanks!
This is a function to retrieve the width:
extension View {
#ViewBuilder func onWidthChange(_ action: #escaping (CGFloat) -> Void) -> some View {
self
.background(
GeometryReader { reader in
Color.clear
.onChange(of: reader.frame(in: .global).width) { newValue in
action(newValue)
}
}
)
}
}
Usage:
Text("Hi")
.onWidthChange { newWidth in
//save width
}
I have a problem related on my List view. The question is simple: how can I get rid of that wierd gray rectangle showing on top of the TabBar? I didn't code that, I just implemented a controller with a List and NavigationBar and then it showed that thing.
For more clear explanation I post the images:
ItemRow.swift code:
import SwiftUI
struct ItemRow: View {
static let colors: [String: Color] = ["D": .purple, "G": .orange, "N": .red, "S": .yellow, "V": .pink]
var item: MenuItem
var body: some View {
NavigationLink(destination: Text(item.name)) {
HStack {
Image(item.thumbnailImage)
.clipShape(Circle())
.overlay(Circle().stroke(Color("IkeaBlu"), lineWidth: 2))
VStack(alignment: .leading){
Text(item.name)
.font(.headline)
Text("€ \(item.price)")
}.layoutPriority(1)
Spacer()
ForEach(item.restrictions, id: \.self) { restriction in
Text(restriction)
.font(.caption)
.fontWeight(.black)
.padding(5)
.background(Self.colors[restriction, default: .black])
.clipShape(Circle())
.foregroundColor(.white)
}
}
}
}
}
struct ItemRow_Previews: PreviewProvider {
static var previews: some View {
ItemRow(item: MenuItem.example)
}
}
thanks a lot for the help
Remove the marked part of hack from TabBar view and that glitch will go.
Tested with Xcode 11.4 / iOS 13.4
} .onAppear {
// UITabBar.appearance().isTranslucent = false // << this one !!
UITabBar.appearance().barTintColor = UIColor(named: "IkeaBlu")
}.accentColor(Color(.white))
I have a view sitting on top of a mapView (in a ZStack) and want to be able to have the green, upper view fade in and out with the .easeInOut animation modifier applied to the view's opacity. As you can see in the gif, it fades in nicely but disappears abruptly.
If I remove the mapView then all is good. If I replace the mapView with a simple Rectangle() then the problem returns so I believe it has something to do with the ZStack rather than the map. Funnily enough I'm actually using Mapbox rather than MapKit (as in the code below for simplicity) and the fade/abrupt disappear behaviour is reversed.
import SwiftUI
import MapKit
struct ContentView: View {
#State private var show = false
var body: some View {
VStack {
ZStack {
MapView()
if show {
LabelView()
.transition(AnyTransition.opacity.animation(.easeInOut(duration: 1.0)))
}
}
Button("Animate") {
self.show.toggle()
}.padding(20)
}
}
}
struct MapView: UIViewRepresentable {
func makeUIView(context: Context) -> MKMapView {
let mapView = MKMapView()
mapView.mapType = .standard
return mapView
}
func updateUIView(_ uiView: MKMapView, context: Context) { }
}
struct LabelView: View {
var body: some View {
Text("Hi there!")
.padding(10)
.font(.title)
.foregroundColor(.white)
.background(RoundedRectangle(cornerRadius: 8).fill(Color.green).shadow(color: .gray, radius: 3))
}
}
I have tried using alternative animation code, replacing the LabelView transition with:
.transition(.opacity)
and changing the button code to:
Button("Animate") {
withAnimation(.easeInOut(duration: 1.0)) {
self.show.toggle()
}
}
but the same behaviour appears each time. I'm guessing this is a SwiftUI bug but couldn't find any previous reference.
Here is working solution. Tested with Xcode 11.4 / iOS 13.4.
As it seen in demo presence of transparent label does not affect functionality of map view.
var body: some View {
VStack {
ZStack {
MapView()
LabelView().opacity(show ? 1 : 0) // here !!
}.animation(.easeInOut(duration: 1.0))
Button("Animate") {
self.show.toggle()
}.padding(20)
}
}
Another alternate, actually with the same visual effect is to embed LabelView into container and apply transition to it (it must be left something to render view disappearing)
var body: some View {
VStack {
ZStack {
MapView()
VStack { // << here !!
if show {
LabelView()
}
}.transition(.opacity).animation(.easeInOut(duration: 1.0))
}
Button("Animate") {
self.show.toggle()
}.padding(20)
}
}
try this: -> just added zIndex ...everything else the same
struct ContentView: View {
#State private var show = false
var body: some View {
VStack {
ZStack {
MapView().zIndex(0)
if show {
LabelView()
.zIndex(1)
.transition(AnyTransition.opacity.animation(.easeInOut(duration: 1.0)))
}
}
Button("Animate") {
self.show.toggle()
}.padding(20)
}
}
}
and read this:
Transition animation not working in SwiftUI
I want to display a label in the centre of a view with a progress indicator to the right of the label. How can I do this in SwiftUI on macOS?
The code below aligns the HStack in the centre of the VStack but I want the text centered and the progress indicator aligned with the text's trailing edge. I guess I could replace the HStack with a ZStack but it's still not clear how one aligns two controls to each other or how one prevents the container from be centered by its container.
import SwiftUI
struct AlignmentTestView: View {
var body: some View {
VStack(alignment: .center, spacing: 4) {
HStack {
Text("Some text")
ActivityIndicator()
}
}.frame(width: 200, height: 200)
.background(Color.pink)
}
}
struct AlignmentTestView_Previews: PreviewProvider {
static var previews: some View {
AlignmentTestView()
}
}
Here is possible approach (tested replacing ActivityIndicator with just circle).
Used Xcode 11.4 / iOS 13.4 / macOS 10.15.4
var body: some View {
VStack {
HStack {
Text("Some text")
.alignmentGuide(.hAlignment) { $0.width / 2.0 }
ActivityIndicator()
}
}
.frame(width: 200, height: 200, alignment:
Alignment(horizontal: .hAlignment, vertical: VerticalAlignment.center))
.background(Color.pink)
}
extension HorizontalAlignment {
private enum HAlignment : AlignmentID {
static func defaultValue(in d: ViewDimensions) -> CGFloat {
return d[HorizontalAlignment.center]
}
}
static let hAlignment = HorizontalAlignment(HAlignment.self)
}