I'm trying to make a menubar app for macOS 12 that shows a popover when the menubar icon is clicked. As you can see from the attached screenshot, I have the popover showing but it's not actually aligned to the menubar icon. Here's the relevant code:
class AppDelegate: NSObject, NSApplicationDelegate {
var popover: NSPopover!
var statusBarItem: NSStatusItem!
func applicationDidFinishLaunching(_ aNotification: Notification) {
// Create the SwiftUI view that provides the window contents.
let contentView = ContentView()
// Create the status item
statusBarItem = NSStatusBar.system.statusItem(withLength: CGFloat(NSStatusItem.variableLength))
// Create the popover
let popover = NSPopover()
popover.behavior = .transient
popover.contentViewController = NSHostingController(rootView: contentView)
self.popover = popover
if let button = statusBarItem.button {
button.image = NSImage(systemSymbolName: "eyes", accessibilityDescription: "Eyes")
button.action = #selector(togglePopover(_:))
}
}
#objc func togglePopover(_ sender: AnyObject?) {
guard let button = statusBarItem.button else { return }
if popover.isShown {
popover.performClose(sender)
} else {
popover.show(relativeTo: button.bounds, of: button, preferredEdge: .maxY)
}
}
}
You can assign contentSize to the popover which is the same size with contentView:
popover.contentViewController = NSHostingController(rootView: contentView)
popover.contentSize = NSSize(width: popoverWidth, height: popoverHeight)
ContentView:
struct ContentView: View {
var body: some View {
VStack {
Text("Hello World")
}
.frame(width: viewWidth, height: viewHeight)
}
}
Beginner here making a simple todo list, but trying to get a blurred background only for the navigation title. I'm trying to do this with and without a UIViewRepresentable struct. Here is my method without the UIViewRepresentable struct.
"""
struct ContentView: View {
init() {
let appearance = UINavigationBarAppearance()
appearance.backgroundEffect = UIBlurEffect(style: .regular)
UINavigationBar.appearance().standardAppearance = appearance
UITableView.appearance().backgroundColor = .clear
}
var body: some View {
VStack {
GeometryReader { geometry in
VStack {
List {
ListEntry()
}
.opacity(0.8)
.frame(height: geometry.size.height*(4/5))
}
VStack {
// empty for now
}
}
}
.background(LeavesBackgroundView())
.navigationTitle(Text("Monday, Apr 26"))
.navigationBarItems(trailing: Image(systemName: "gear"))
}
}
"""
..now with the UIViewRepresentable struct:
"""
struct theBlurView: UIViewRepresentable {
#State var style: UIBlurEffect.Style = .systemMaterial
func makeUIView(context: Context) -> UIVisualEffectView {
let view = UIVisualEffectView(effect: UIBlurEffect(style: style))
return view
}
func updateUIView(_ uiView: UIVisualEffectView, context: Context) {
uiView.effect = UIBlurEffect(style: style)
}
}
struct ContentView: View {
init() {
let appearance = UINavigationBarAppearance()
appearance.backgroundEffect = theBlurView(style: .regular) // error right here
UINavigationBar.appearance().standardAppearance = appearance
UITableView.appearance().backgroundColor = .clear
}
var body: some View {
VStack {
GeometryReader { geometry in
VStack {
List {
ListEntry()
}
.opacity(0.8)
.frame(height: geometry.size.height*(4/5))
}
VStack {
// empty for now
}
}
}
.background(LeavesBackgroundView())
.navigationTitle(Text("Monday, Apr 26"))
.navigationBarItems(trailing: Image(systemName: "gear"))
}
}
"""
In the second case, I get the error "Cannot assign value of type 'theBlurView' to type 'UIBlurEffect?'", but I cannot figure out a way to get them to be the same type.
In the first case, I get no error, but I get a white opaque navigation title background.
In both cases, I get this
this
where the navigation title background is white. I've also tried different material styles (.dark, .light, .systemChromeMaterial, etc) and nothing makes it blurry.
This is the kind of blur I'm trying to get
Can somebody please point me in the right direction?
If you accept a third party library:
Install SwiftUIX
Make blur with few lines of code by modify your NavBar .background(VisualEffectBlurView(blurStyle: .systemThinMaterial)) and dont forget import SwiftUIX befor using.
TextEditor seems to have a default white background. So the following is not working and it displayed as white instead of defined red:
var body: some View {
TextEditor(text: .constant("Placeholder"))
.background(Color.red)
}
Is it possible to change the color to a custom one?
iOS 16
You should hide the default background to see your desired one:
TextEditor(text: .constant("Placeholder"))
.scrollContentBackground(.hidden) // <- Hide it
.background(.red) // To see this
iOS 15 and below
TextEditor is backed by UITextView. So you need to get rid of the UITextView's backgroundColor first and then you can set any View to the background.
struct ContentView: View {
init() {
UITextView.appearance().backgroundColor = .clear
}
var body: some View {
List {
TextEditor(text: .constant("Placeholder"))
.background(.red)
}
}
}
Demo
You can find my simple trick for growing TextEditor here in this answer
Pure SwiftUI solution on iOS and macOS
colorMultiply is your friend.
struct ContentView: View {
#State private var editingText: String = ""
var body: some View {
TextEditor(text: $editingText)
.frame(width: 400, height: 100, alignment: .center)
.cornerRadius(3.0)
.colorMultiply(.gray)
}
}
Update iOS 16 / SwiftUI 4.0
You need to use .scrollContentBackground(.hidden) instead of UITextView.appearance().backgroundColor = .clear
https://twitter.com/StuFFmc/status/1556561422431174656
Warning: This is an iOS 16 only so you'll probably need some if #available and potentially two different TextEditor component.
extension View {
/// Layers the given views behind this ``TextEditor``.
func textEditorBackground<V>(#ViewBuilder _ content: () -> V) -> some View where V : View {
self
.onAppear {
UITextView.appearance().backgroundColor = .clear
}
.background(content())
}
}
Custom Background color with SwiftUI on macOS
On macOS, unfortunately, you have to fallback to AppKit and wrap NSTextView.
You need to declare a view that conforms to NSViewRepresentable
This should give you pretty much the same behaviour as SwiftUI's TextEditor-View and since the wrapped NSTextView does not draw its background, you can use the .background-ViewModifier to change the background
struct CustomizableTextEditor: View {
#Binding var text: String
var body: some View {
GeometryReader { geometry in
NSScrollableTextViewRepresentable(text: $text, size: geometry.size)
}
}
}
struct NSScrollableTextViewRepresentable: NSViewRepresentable {
typealias Representable = Self
// Hook this binding up with the parent View
#Binding var text: String
var size: CGSize
// Get the UndoManager
#Environment(\.undoManager) var undoManger
// create an NSTextView
func makeNSView(context: Context) -> NSScrollView {
// create NSTextView inside NSScrollView
let scrollView = NSTextView.scrollableTextView()
let nsTextView = scrollView.documentView as! NSTextView
// use SwiftUI Coordinator as the delegate
nsTextView.delegate = context.coordinator
// set drawsBackground to false (=> clear Background)
// use .background-modifier later with SwiftUI-View
nsTextView.drawsBackground = false
// allow undo/redo
nsTextView.allowsUndo = true
return scrollView
}
func updateNSView(_ scrollView: NSScrollView, context: Context) {
// get wrapped nsTextView
guard let nsTextView = scrollView.documentView as? NSTextView else {
return
}
// fill entire given size
nsTextView.minSize = size
// set NSTextView string from SwiftUI-Binding
nsTextView.string = text
}
// Create Coordinator for this View
func makeCoordinator() -> Coordinator {
Coordinator(self)
}
// Declare nested Coordinator class which conforms to NSTextViewDelegate
class Coordinator: NSObject, NSTextViewDelegate {
var parent: Representable // store reference to parent
init(_ textEditor: Representable) {
self.parent = textEditor
}
// delegate method to retrieve changed text
func textDidChange(_ notification: Notification) {
// check that Notification.name is of expected notification
// cast Notification.object as NSTextView
guard notification.name == NSText.didChangeNotification,
let nsTextView = notification.object as? NSTextView else {
return
}
// set SwiftUI-Binding
parent.text = nsTextView.string
}
// Pass SwiftUI UndoManager to NSTextView
func undoManager(for view: NSTextView) -> UndoManager? {
parent.undoManger
}
// feel free to implement more delegate methods...
}
}
Usage
ContenView: View {
#State private var text: String
var body: some View {
VStack {
Text("Enter your text here:")
CustomizableTextEditor(text: $text)
.background(Color.red)
}
.frame(minWidth: 600, minHeight: 400)
}
}
Edit:
Pass reference to SwiftUI UndoManager so that default undo/redo actions are available.
Wrap NSTextView in NSScrollView so that it is scrollable. Set minSize property of NSTextView to enclosing SwiftUIView-Size so that it fills the entire allowed space.
Caveat: Only first line of this custom TextEditor is clickable to enable text editing.
This works for me on macOS
extension NSTextView {
open override var frame: CGRect {
didSet {
backgroundColor = .clear
drawsBackground = true
}
}
}
struct ContentView: View {
#State var text = ""
var body: some View {
TextEditor(text: $text)
.background(Color.red)
}
Reference this answer
To achieve this visual design here is the code I used.
iOS 16
TextField(
"free_form",
text: $comment,
prompt: Text("Type your feedback..."),
axis: .vertical
)
.lineSpacing(10.0)
.lineLimit(10...)
.padding(16)
.background(Color.themeSeashell)
.cornerRadius(16)
iOS 15
ZStack(alignment: .topLeading) {
RoundedRectangle(cornerRadius: 16)
.foregroundColor(.gray)
TextEditor(text: $comment)
.padding()
.focused($isFocused)
if !isFocused {
Text("Type your feedback...")
.padding()
}
}
.frame(height: 132)
.onAppear() {
UITextView.appearance().backgroundColor = .clear
}
You can use Mojtaba's answer (the approved answer). It works in most cases. However, if you run into this error:
"Return from initializer without initializing all stored properties"
when trying to use the init{ ... } method, try adding UITextView.appearance().backgroundColor = .clear to .onAppear{ ... } instead.
Example:
var body: some View {
VStack(alignment: .leading) {
...
}
.onAppear {
UITextView.appearance().backgroundColor = .clear
}
}
Using the Introspect library, you can use .introspectTextView for changing the background color.
TextEditor(text: .constant("Placeholder"))
.cornerRadius(8)
.frame(height: 100)
.introspectTextView { textView in
textView.backgroundColor = UIColor(Color.red)
}
Result
import SwiftUI
struct AddCommentView: View {
init() {
UITextView.appearance().backgroundColor = .clear
}
var body: some View {
VStack {
if #available(iOS 16.0, *) {
TextEditor(text: $viewModel.commentText)
.scrollContentBackground(.hidden)
} else {
TextEditor(text: $viewModel.commentText)
}
}
.background(Color.blue)
.frame(height: UIScreen.main.bounds.width / 2)
.overlay(
RoundedRectangle(cornerRadius: 5)
.stroke(Color.red, lineWidth: 1)
)
}
}
It appears the UITextView.appearance().backgroundColor = .clear trick in IOS 16,
only works for the first time you open the view and the effect disappear when the second time it loads.
So we need to provide both ways in the app. Answer from StuFF mc works.
var body: some View {
if #available(iOS 16.0, *) {
mainView.scrollContentBackground(.hidden)
} else {
mainView.onAppear {
UITextView.appearance().backgroundColor = .clear
}
}
}
// rename body to mainView
var mainView: some View {
TextEditor(text: $notes).background(Color.red)
}
I have the following:
var body: some View {
NavigationView {
VStack {
Text("Hello")
}.navigationBarTitle("Edit Profile", displayMode: .inline)
.background(NavigationConfiguration { nc in
nc.navigationBar.barTintColor = .red
nc.navigationBar.titleTextAttributes = [.foregroundColor: UIColor.black]
})
}.navigationViewStyle(StackNavigationViewStyle())
}
For the configuration of the nav bar i have:
struct NavigationConfiguration: UIViewControllerRepresentable {
var configuration: (UINavigationController) -> Void = { _ in }
func makeUIViewController(context: UIViewControllerRepresentableContext<NavigationConfiguration>) -> UIViewController {
UIViewController()
}
func updateUIViewController(_ uiViewController: UIViewController, context: UIViewControllerRepresentableContext<NavigationConfiguration>) {
if let nc = uiViewController.navigationController {
self.configuration(nc)
}
}
}
But for some reason the top status bar is being left out and the colour is not being applied to it:
How can the red colour also be applied to the status bar above the nav bar, I want it to be the same colour.
I've tried the below, but this fails:
init() {
UINavigationBar.appearance().backgroundColor = .red
}
Try the following (it should work, at least as tested with Xcode 11.4 / iOS 13.4, but someone reported it was not, so it might be dependent)
init() {
let navBarAppearance = UINavigationBarAppearance()
navBarAppearance.configureWithOpaqueBackground()
navBarAppearance.backgroundColor = UIColor.systemRed
UINavigationBar.appearance().standardAppearance = navBarAppearance
}
EDIT 1:
Checkout my new answer: https://stackoverflow.com/a/62031788/8023700
It has detailed description and screenshot as well.
Try using this modifier as this gives you freedom to change color based on each view.
struct NavigationBarModifier: ViewModifier {
var backgroundColor: UIColor = .clear
init(backgroundColor: UIColor, tintColor: UIColor = .white) {
self.backgroundColor = backgroundColor
let coloredAppearance = UINavigationBarAppearance()
coloredAppearance.backgroundColor = backgroundColor
coloredAppearance.titleTextAttributes = [.foregroundColor: UIColor.white]
coloredAppearance.largeTitleTextAttributes = [.foregroundColor: UIColor.white]
UINavigationBar.appearance().standardAppearance = coloredAppearance
UINavigationBar.appearance().compactAppearance = coloredAppearance
UINavigationBar.appearance().scrollEdgeAppearance = coloredAppearance
UINavigationBar.appearance().tintColor = tintColor
}
func body(content: Content) -> some View {
ZStack{
content
VStack {
GeometryReader { geometry in
Color(self.backgroundColor)
.frame(height: geometry.safeAreaInsets.top)
.edgesIgnoringSafeArea(.top)
Spacer()
}
}
}
}}
I used it on below View:
struct DummyNavigationView: View {
var body: some View {
NavigationView {
VStack {
NavigationLink(destination: Text("Page 2")) {
Text("Go to detail")
}
}
.navigationBarTitle("Edit Profile", displayMode: .inline)
}
.modifier(NavigationBarModifier(backgroundColor: .red, tintColor: .black))
}}
So....I have a lot of code, and in an initialization of a view, I change the UINavigationBar:
struct ContentView: View {
init() {
UINavigationBar.appearance().backgroundColor = .black
}
var body: some View {
NavigationView {
VStack {
Text("Test")
}.navigationBarTitle("TestBarTitle", displayMode: .inline)
}
}
}
For some reason, this only makes the bar appear gray (almost like it's applying a transparent black filter). I am not sure but I think there must be some code that is messing up this navigation bar change. What might have possibly caused this? I'd like the navBar to literally be black.
Side Note: When I remove displayMode: .inline, the navBar appears as a solid color instead of transparent...how do I maintain the navBar setup in the way that displayMode: .inline provides though?
We can create a custom modifier called ".navigationBarColor()" and use it like so:
struct ContentView: View {
var body: some View {
NavigationView {
VStack {
Text("Test")
}
.navigationBarTitle("TestBarTitle", displayMode: .inline)
.navigationBarColor(.black)
}
}
}
Add this to your ContentView file:
struct NavigationBarModifier: ViewModifier {
var backgroundColor: UIColor?
init( backgroundColor: UIColor?) {
self.backgroundColor = backgroundColor
let coloredAppearance = UINavigationBarAppearance()
coloredAppearance.configureWithTransparentBackground()
coloredAppearance.backgroundColor = .clear
coloredAppearance.titleTextAttributes = [.foregroundColor: UIColor.white]
coloredAppearance.largeTitleTextAttributes = [.foregroundColor: UIColor.white]
UINavigationBar.appearance().standardAppearance = coloredAppearance
UINavigationBar.appearance().compactAppearance = coloredAppearance
UINavigationBar.appearance().scrollEdgeAppearance = coloredAppearance
UINavigationBar.appearance().tintColor = .white
}
func body(content: Content) -> some View {
ZStack{
content
VStack {
GeometryReader { geometry in
Color(self.backgroundColor ?? .clear)
.frame(height: geometry.safeAreaInsets.top)
.edgesIgnoringSafeArea(.top)
Spacer()
}
}
}
}
}
extension View {
func navigationBarColor(_ backgroundColor: UIColor?) -> some View {
self.modifier(NavigationBarModifier(backgroundColor: backgroundColor))
}
}
Check out this article which was posted on March 10, 2020.
https://filipmolcik.com/navigationview-dynamic-background-color-in-swiftui/