I want to add a NSVisualEffectView inside my NSTextView, however when I add it, the text is below the NSVisualEffectView, so, how can i add the NSVisualEffectView below the text?
My code for OS X:
class myTextView: NSTextView {
override func awakeFromNib() {
let visualEffectView = NSVisualEffectView(frame: NSMakeRect(20, 20, 30, 18))
visualEffectView.material = NSVisualEffectMaterial.Dark
visualEffectView.blendingMode = NSVisualEffectBlendingMode.BehindWindow
self.addSubview(visualEffectView)
}
}
Maybe this will help you:
class MyTextView: NSTextView {
lazy var visualEffectView: NSVisualEffectView = {
let visualEffectView = NSVisualEffectView()
visualEffectView.material = NSVisualEffectMaterial.Dark
visualEffectView.blendingMode = NSVisualEffectBlendingMode.BehindWindow
return visualEffectView
}()
override func awakeFromNib() {
super.awakeFromNib()
self.drawsBackground = false
}
override func viewDidMoveToSuperview() {
super.viewDidMoveToSuperview()
if let containerView = self.superview?.superview?.superview {
containerView.addSubview(self.visualEffectView, positioned: .Below, relativeTo: self)
}
}
override func resizeSubviewsWithOldSize(oldSize: NSSize) {
super.resizeSubviewsWithOldSize(oldSize)
if let superview = self.superview?.superview {
self.visualEffectView.frame = superview.frame
}
}
}
It's a text view in a scroll view and clip view so you have to get its superview.
self.sendSubviewToBack(visualEffectView)
But the visual effect needs to be applied to the parent container, not the text view itself, so if I'm understanding your code correctly you need to move this whole thing up to whatever the parent view is.
Related
I am trying to make NSViewControler work with NSView. I can't get myView to call any function in myView(NSView).
myView?.myFunctionName does not work and for NSView I get a nil error when I try set backgoundColor (myview?.layer?.backgroundColor = .white)
class ViewController: NSViewController {
#IBOutlet weak public var myview: NSView!
override func viewDidLoad() {
super.viewDidLoad()
myview?.wantsLayer = true
myview?.layer?.backgroundColor = .white
myview?.needsDisplay = true
}
override var representedObject: Any? {
didSet {
//Update the view, if already loaded.
}
}
}
import Cocoa
class myView: NSView {
var backgroundColor: NSColor?
override func draw(_ dirtyRect: NSRect) {
print("draw")
if let bg = backgroundColor {
bg.setFill()
dirtyRect.fill()
} else {
super.draw(dirtyRect)
print("error:\(String(describing: backgroundColor))")
self.changeBackgroundColor(color: .blue)
}
}
func changeBackgroundColor(color: NSColor) {
// print("change background\( bounds.size)")
// backgroundColor = color
var path = NSBezierPath()
path = NSBezierPath(rect: NSRect(origin:CGPoint(x: 0, y: 0), size: bounds.size))
// print("\(String(describing: path))")
path.close()
color.setFill()
path.fill()
setNeedsDisplay(self.bounds)
}
}
I am working with SwiftUI 1.0.
I have created a search bar for SwiftUI as the following:
import SwiftUI
struct Searchbar: NSViewRepresentable {
class Coordinator: NSObject, NSSearchFieldDelegate {
var parent: Searchbar
init(_ parent: Searchbar) {
self.parent = parent
}
func controlTextDidChange(_ notification: Notification) {
guard let searchField = notification.object as? NSSearchField else {
log.error("Unexpected control in update notification", source: .ui)
return
}
self.parent.search = searchField.stringValue
}
}
#Binding var search: String
func makeNSView(context: Context) -> NSSearchField {
let searchfield = NSSearchField(frame: .zero)
return searchfield
}
func changeSearchFieldItem(searchfield: NSSearchField, sender: AnyObject) -> NSSearchField {
//Based on the Menu item selection in the search field the placeholder string is set
(searchfield.cell as? NSSearchFieldCell)?.placeholderString = sender.title
return searchfield
}
func updateNSView(_ searchField: NSSearchField, context: Context) {
searchField.stringValue = search
searchField.delegate = context.coordinator
}
func makeCoordinator() -> Coordinator {
return Coordinator(self)
}
}
This is working fine so far when using it in my View:
Searchbar(search: $searchText)
I am wondering if the height of the NSSearchField can be changed to have a view similar to what is seen in the Maps.app:
Update: You can also set the controlSize to .large if you’re on Big Sur.
https://developer.apple.com/documentation/appkit/nscontrol/controlsize/large
You can add a height constraint:
func makeNSView(context: Context) -> NSSearchField {
let searchfield = NSSearchField(frame: .zero)
searchfield.translatesAutoresizingMaskIntoConstraints = false
searchfield.heightAnchor.constraint(greaterThanOrEqualToConstant: 40).isActive = true
return searchfield
}
…which works in macOS 11.1 Big Sur. Unfortunately the Focus ring does not adapt its height. You could hide it like this:
searchTextField.focusRingType = .none
… but that does not seem desirable in most situations.
My problem is shown in the photo below:
I have tried using .fixedSize(horizontal:vertical) on the parent view and the textfield and no positive results.
TheTextField:
struct TheTextField: UIViewRepresentable {
#Binding var text : String
#Binding var placeholder : String
func makeCoordinator() -> TheTextField.Coordinator {
return TheTextField.Coordinator(parent1: self)
}
func makeUIView(context: UIViewRepresentableContext<TheTextField>) -> UITextView {
let tview = UITextView()
tview.isEditable = true
tview.isUserInteractionEnabled = true
tview.isScrollEnabled = false
tview.text = placeholder
tview.textColor = .gray
tview.font = .systemFont(ofSize: 20)
tview.delegate = context.coordinator
return tview
}
func updateUIView(_ uiView: UITextView, context: UIViewRepresentableContext<TheTextField>) {
}
class Coordinator : NSObject, UITextViewDelegate {
var parent : TheTextField
init(parent1 : TheTextField) {
parent = parent1
}
func textViewDidChange(_ textView: UITextView) {
self.parent.text = textView.text
}
func textViewDidBeginEditing(_ textView: UITextView) {
textView.text = ""
textView.textColor = .label
}
}
}
I want the textview to not expand and move the cursor to the next line, instead of messing up the parent's view.
Example:
Divider()
TheTextField(text: self.$imageToUpload.textCaption, placeholder: self.$placeholder).padding(.horizontal)
Spacer()
import SwiftUI
struct UITextViewWrapper: UIViewRepresentable {
#Binding var text : String
#Binding var calculatedHeight: CGFloat
func makeCoordinator() -> UITextViewWrapper.Coordinator {
return UITextViewWrapper.Coordinator(text: $text, height: $calculatedHeight)
}
func makeUIView(context: UIViewRepresentableContext<UITextViewWrapper>) -> UITextView {
let tview = UITextView()
tview.isEditable = true
tview.isUserInteractionEnabled = true
tview.isScrollEnabled = false
tview.textColor = .gray
tview.font = .systemFont(ofSize: 20)
tview.delegate = context.coordinator
tview.setContentCompressionResistancePriority(.defaultLow, for: .horizontal)
return tview
}
func updateUIView(_ uiView: UITextView, context: UIViewRepresentableContext<UITextViewWrapper>) {
if uiView.text != self.text {
uiView.text = self.text
}
if uiView.window != nil, uiView.isFirstResponder {
uiView.becomeFirstResponder()
}
}
static func recalculateHeight(view: UIView, result: Binding<CGFloat>){
let newSize = view.sizeThatFits(CGSize(width: view.frame.size.width, height: CGFloat.greatestFiniteMagnitude))
if result.wrappedValue != newSize.height {
DispatchQueue.main.async {
result.wrappedValue = newSize.height // !! must be called asynchronously
}
}
}
class Coordinator : NSObject, UITextViewDelegate {
var text : Binding<String>
var calculatedHeight : Binding<CGFloat>
init(text: Binding<String>, height: Binding<CGFloat>) {
self.text = text
self.calculatedHeight = height
}
func textViewDidChange(_ textView: UITextView) {
text.wrappedValue = textView.text
UITextViewWrapper.recalculateHeight(view: textView, result: calculatedHeight)
}
func textViewDidBeginEditing(_ textView: UITextView) {
textView.text = ""
textView.textColor = .label
}
}
}
I added functionality for changing the height of the dynamically depending on the length of the text per suggestion by Asperi.
You can easily edit label property from .storyboard file of our project.
Change lines to 0 and line break property to your desired type and then you can have as many lines as you want your label to have.Check this image to see what I'm saying
I have a view controller MyViewController:
class MyViewController: NSViewController {
private let componentList = ComponentList()
override func loadView() {
componentList.createView(view)
componentList.myTableView.doubleAction = #selector(doubleClickOnRow)
}
#objc func doubleClickOnRow() {
print("some row clicked = \(componentList.myTableView.clickedRow)")
}
}
This double click action works without problem. However, when I try to put this double click action inside ComponentList, it's not working (action function is not called):
class ComponentList: NSObject, NSTableViewDelegate, NSTableViewDataSource {
let myTableView = NSTableView()
override func createView(view: NSView) {
let scrollView = NSScrollView()
view.addSubview(scrollView)
scrollView.documentView = myTableView
// set up some constraints, ignore here...
myTableView.delegate = self
myTableView.dataSource = self
myTableView.doubleAction = #selector(doubleClickOnRow)
}
#objc func doubleClickOnRow() {
print("some row clicked = \(myTableView.clickedRow)")
// never being called, why is that?
}
}
Why isn't the double action handling in ComponentList not working? Am I missing something here?
You need to set both target and doubleAction to make it work with ComponentList
override func createView(view: NSView) {
let scrollView = NSScrollView()
view.addSubview(scrollView)
scrollView.documentView = myTableView
// set up some constraints, ignore here...
myTableView.delegate = self
myTableView.dataSource = self
myTableView.doubleAction = #selector(doubleClickOnRow)
myTableView.target = self // self here is ComponentList
}
The NSScrollView containing a NSStackView. The NSStackView's items will add in runtime.The new NSStackView's items are from bottom to top, but I want they are from top to bottom.
The main.storyboard
The ViewController
import Cocoa
class ViewController: NSViewController {
var todoList:[Todo] = []
#IBOutlet weak var todosStackView: NSStackView!
#IBOutlet weak var todosScrollView: NSScrollView!
#IBAction func onEnter(sender: NSTextField) {
let todo = Todo()
todo.content = sender.stringValue
self.todoList.append(todo)
let todoItemView = TodoItem()
todoItemView.setButtonType(.SwitchButton)
todoItemView.todo=todo
todosStackView.addView(todoItemView, inGravity: .Top)
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
override var representedObject: AnyObject? {
didSet {
// Update the view, if already loaded.
}
}
}
The checkbox button class
import Cocoa
class TodoItem: NSButton {
var todo:Todo!{
didSet {
self.title = self.todo.content
self.state = self.todo.done ? NSOnState : NSOffState
}
}
func completeTodo(){
if(self.state == NSOnState){
self.todo.done = true
}else{
self.todo.done = false
}
}
required init?(coder: NSCoder) {
super.init(coder: coder)
}
override init(frame frameRect: NSRect) {
super.init(frame: frameRect)
self.setButtonType(.SwitchButton)
self.target = self
self.action = #selector(self.completeTodo)
}
}
By default, NSClipViews (and the base NSView) are not flipped and so coordinates (and scroll view positioning) are relative to the bottom left.
You can subclass NSClipView and override isFlipped to return true. Then in IB, set the clip view of the scroll view to that custom class.
You have your view (the parent of stack view) anchored to parent clip view via leading and bottom anchor. Try using leading and top instead. Note: your stack view itself seems to be aligned properly.