Label and NSView in a vibrant background - swift

I have created a window with a vibrant background (kind of semi-transparent background).
override func viewWillAppear() {
super.viewWillAppear()
let visualEffect = NSVisualEffectView()
visualEffect.blendingMode = .behindWindow
visualEffect.state = .active
visualEffect.material = .light
view.window?.contentView = visualEffect
}
When I add that vibrant background, the text of the label disappears.
How to put a label and an NSView in a vibrant background?
(To test it I have created a new document with a label with no background and that text: label1 and only the code of the vibrant background I give here. When I add the vibrant code, the text of the label disappears)

// That is what I already had in my question:
let visualEffect = NSVisualEffectView()
visualEffect.blendingMode = NSVisualEffectView.BlendingMode.behindWindow
visualEffect.material = NSVisualEffectView.Material.light
// That is the key to solve the problem:
view.addSubview(visualEffect, positioned: .below, relativeTo: label1)
// Constraints. They are also necessary:
visualEffect.translatesAutoresizingMaskIntoConstraints = false
visualEffect.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
visualEffect.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true
visualEffect.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
visualEffect.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true

Related

Why doesn't my UILabel in a nested view receive touch events / How can I test the Responder Chain?

I have found lots of similar questions about not receiving touch events and I understand that in some cases, writing a custom hitTest function may be required - but I also read that the responder chain will traverse views and viewControllers that are in the hierarchy - and I don't understand why a custom hitTest would be required for my implementation.
I'm looking for an explanation and/or a link to a document that explains how to test the responder chain. This problem is occurring in Xcode 10.2.1.
My scenario (I am not using Storyboard):
I have a mainViewController, that provides a full screen view with an ImageView and a few Labels. I have attached TapGestureRecognizers to the ImageView and one of the labels - and they both work properly.
When I tap the label, I add a child viewController and it's view as a subview to the mainViewController. The view is constrained to cover only the right-half of the screen.
The child viewController contains a vertical stack view that contains 3 arrangedSubviews.
Each arrangedSubview contains a Label and a horizontal StackView.
The horizontal stackView's each contain a View with a Label as a subview.
The Label in the subview sets it's isUserInteractionEnabled flag to True and adds a TapGestureRecognizer.
These are the only objects in the child ViewController that have 'isUserInteractionEnabled' set.
The Label's are nested fairly deep, but since this is otherwise a direct parent/child hierarchy (as opposed to the 2 views belonging to a NavigationController), I would expect the Label's to be in the normal responder chain and function properly. Do the Stack View's change that behavior? Do I need to explicitly set the 'isUserInteractionEnabled' value to False on some of the views? Is there way I can add logging to the ResponderChain so I can see which views it checked and find out where it is being blocked?
After reading this StackOverflow post I tried adding my gesture recognizers in viewDidLayoutSubviews() instead of what's shown below - but they still do not receive tap events.
Thank you in advance to any who can offer advice or help.
Here is the code for the label that is not responding to my tap events and the tap event it should call:
func makeColorItem(colorName:String, bgColor:UIColor, fgColor:UIColor) -> UIView {
let colorNumber:Int = colorLabelDict.count
let colorView:UIView = {
let v = UIView()
v.tag = 700 + colorNumber
v.backgroundColor = .clear
v.contentMode = .center
return v
}()
self.view.addSubview(colorView)
let tapColorGR:UITapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(tapColor))
let colorChoice: UILabel = {
let l = UILabel()
l.tag = 700 + colorNumber
l.isUserInteractionEnabled = true
l.addGestureRecognizer(tapColorGR)
l.text = colorName
l.textAlignment = .center
l.textColor = fgColor
l.backgroundColor = bgColor
l.font = UIFont.systemFont(ofSize: 24, weight: .bold)
l.layer.borderColor = fgColor.cgColor
l.layer.borderWidth = 1
l.layer.cornerRadius = 20
l.layer.masksToBounds = true
l.adjustsFontSizeToFitWidth = true
l.translatesAutoresizingMaskIntoConstraints = false
l.widthAnchor.constraint(equalToConstant: 100)
return l
}()
colorView.addSubview(colorChoice)
colorChoice.centerXAnchor.constraint(equalTo: colorView.centerXAnchor).isActive = true
colorChoice.centerYAnchor.constraint(equalTo: colorView.centerYAnchor).isActive = true
colorChoice.heightAnchor.constraint(equalToConstant: 50).isActive = true
colorChoice.widthAnchor.constraint(equalToConstant: 100).isActive = true
colorLabelDict[colorNumber] = colorChoice
return colorView
}
#objc func tapColor(sender:UITapGestureRecognizer) {
print("A Color was tapped...with tag:\(sender.view?.tag ?? -1)")
if let cn = sender.view?.tag {
colorNumber = cn
let v = colorLabelDict[cn]
if let l = (v?.subviews.first as? UILabel) {
print("The \(l.text) label was tapped.")
}
}
}
It looks like the main reason you're not getting a tap recognized is because you are adding a UILabel as a subview of a UIView, but you're not giving that UIView any constraints. So the view ends up with a width and height of Zero, and the label exists outside the bounds of the view.
Without seeing all of your code, it doesn't look like you need the extra view holding the label.
Take a look at this... it will add a vertical stack view to the main view - centered X and Y - and add "colorChoice" labels to the stack view:
class TestViewController: UIViewController {
let stack: UIStackView = {
let v = UIStackView()
v.axis = .vertical
v.spacing = 4
return v
}()
var colorLabelDict: [Int: UIView] = [:]
override func viewDidLoad() {
super.viewDidLoad()
let v1 = makeColorLabel(colorName: "red", bgColor: .red, fgColor: .white)
let v2 = makeColorLabel(colorName: "green", bgColor: .green, fgColor: .black)
let v3 = makeColorLabel(colorName: "blue", bgColor: .blue, fgColor: .white)
[v1, v2, v3].forEach {
stack.addArrangedSubview($0)
}
stack.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(stack)
NSLayoutConstraint.activate([
stack.centerXAnchor.constraint(equalTo: view.centerXAnchor),
stack.centerYAnchor.constraint(equalTo: view.centerYAnchor),
])
}
func makeColorLabel(colorName:String, bgColor:UIColor, fgColor:UIColor) -> UILabel {
let colorNumber:Int = colorLabelDict.count
// create tap gesture recognizer
let tapColorGR:UITapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(tapColor))
let colorChoice: UILabel = {
let l = UILabel()
l.tag = 700 + colorNumber
l.addGestureRecognizer(tapColorGR)
l.text = colorName
l.textAlignment = .center
l.textColor = fgColor
l.backgroundColor = bgColor
l.font = UIFont.systemFont(ofSize: 24, weight: .bold)
l.layer.borderColor = fgColor.cgColor
l.layer.borderWidth = 1
l.layer.cornerRadius = 20
l.layer.masksToBounds = true
l.adjustsFontSizeToFitWidth = true
l.translatesAutoresizingMaskIntoConstraints = false
// default .isUserInteractionEnabled for UILabel is false, so enable it
l.isUserInteractionEnabled = true
return l
}()
NSLayoutConstraint.activate([
// label height: 50, width: 100
colorChoice.heightAnchor.constraint(equalToConstant: 50),
colorChoice.widthAnchor.constraint(equalToConstant: 100),
])
// assign reference to this label in colorLabelDict dictionary
colorLabelDict[colorNumber] = colorChoice
// return newly created label
return colorChoice
}
#objc func tapColor(sender:UITapGestureRecognizer) {
print("A Color was tapped...with tag:\(sender.view?.tag ?? -1)")
// unwrap the view that was tapped, make sure it's a UILabel
guard let tappedView = sender.view as? UILabel else {
return
}
let cn = tappedView.tag
let colorNumber = cn
print("The \(tappedView.text ?? "No text") label was tapped.")
}
}
Result of running that:
Those are 3 UILabels, and tapping each will trigger the tapColor() func, printing this to the debug console:
A Color was tapped...with tag:700
The red label was tapped.
A Color was tapped...with tag:701
The green label was tapped.
A Color was tapped...with tag:702
The blue label was tapped.

How do I make a PDFView start at the top of the document?

Currently I am using a PDFView in my swift application, however when I instantiate the view controller that uses the PDFView, it always opens the pdf slightly scrolled down. For example:
The first image is how it opens, and the second image is the way I would like it to open. I can simply scroll up to have it look like the image on the right, however I wanted to know if there was any way of automatically doing it.
Here is the code I am currently using:
override func viewDidLoad() {
let pdfView = PDFView()
pdfView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(pdfView)
pdfView.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor).isActive = true
pdfView.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor).isActive = true
pdfView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor).isActive = true
pdfView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor).isActive = true
pdfView.autoScales = true
pdfView.displayMode = .singlePageContinuous
pdfView.displayDirection = .vertical
guard let url = item?.url else {
return
}
guard let path = Bundle.main.url(forResource: url, withExtension: "pdf") else { return }
if let document = PDFDocument(url: path) {
pdfView.document = document
}
}
I have the same issue and I solve it adding the following line:
override func viewDidLoad() {
super.viewDidLoad()
// this line
self.edgesForExtendedLayout = []
let pdfView = PDFView()()
...
}
That line stops your view going under the navigation bar
I had the same issue with my PDF Document.
It looks like the document within the PDF Viewer simply ignored the top-anchor of the PDF Viewer and ended up favouring the top of the device and thus you have the top part of the document under the navigation bar.
The way I solved it was to add a UIView to the view-controller and anchor it all around to the 'safeAreaLayoutGuide' of the view-controller, then add the PDFView to that UIView and pin it to all the edges. Seems like a long winded approach, but it works.
The code below shows the basic implementation.
lazy var pdfView: PDFView = {
let pdf = PDFView(frame: self.view.bounds)
pdf.displayMode = .singlePageContinuous
pdf.displayDirection = .vertical
pdf.scaleFactor = pdf.scaleFactorForSizeToFit
pdf.autoScales = true
pdf.translatesAutoresizingMaskIntoConstraints = false
pdf.setValue(true, forKey: "forcesTopAlignment")
return pdf
}()
let pdfContentView = UIView()
view.addSubview(pdfContentView)
pdfContentView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([pdfContentView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor),
pdfContentView.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor),
pdfContentView.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor),
pdfContentView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor)])
pdfContentView.addSubview(pdfView)
NSLayoutConstraint.activate([pdfView.topAnchor.constraint(equalTo: pdfContentView.topAnchor),
pdfView.bottomAnchor.constraint(equalTo: pdfContentView.bottomAnchor),
pdfView.widthAnchor.constraint(equalTo: pdfContentView.widthAnchor),
pdfView.centerXAnchor.constraint(equalTo: pdfContentView.centerXAnchor)])

Making Cocoa NSWindow translucent hides storyboard elements

In my app delegate I make my window translucent with the following code:
func applicationDidFinishLaunching(_ aNotification: Notification) {
let visualEffect = NSVisualEffectView()
visualEffect.translatesAutoresizingMaskIntoConstraints = false
visualEffect.material = .dark
visualEffect.state = .active
visualEffect.wantsLayer = true
visualEffect.layer?.cornerRadius = 16.0
NSApplication.shared.mainWindow?.titleVisibility = .hidden
NSApplication.shared.mainWindow?.styleMask.remove(.titled)
NSApplication.shared.mainWindow?.backgroundColor = .clear
NSApplication.shared.mainWindow?.isMovableByWindowBackground = true
NSApplication.shared.mainWindow?.contentView?.addSubview(visualEffect)
guard let constraints = NSApplication.shared.mainWindow?.contentView else {
return
}
visualEffect.leadingAnchor.constraint(equalTo: constraints.leadingAnchor).isActive = true
visualEffect.trailingAnchor.constraint(equalTo: constraints.trailingAnchor).isActive = true
visualEffect.topAnchor.constraint(equalTo: constraints.topAnchor).isActive = true
visualEffect.bottomAnchor.constraint(equalTo: constraints.bottomAnchor).isActive = true
}
The problem with this is that every element in the storyboard is no longer visible. How can I fix this? Thanks
You need to add all your UI to the NSVisualEffectView as subviews or move the NSVisualEffectView to the back of the view hierarchy:
NSApplication.shared.mainWindow?.contentView?.addSubview(visualEffect, positioned: .below, relativeTo: nil)
Update:
I created a new macOS project in Xcode and added a label on the view. Then I pasted your code and the only thing I changed was the line of code above. It works.

Set view background from image view

I am trying to match whatever image is placed in a UIImageView with the background image of the view controller's view. So when the user calls func call in the example below, whatever image is in the image view choosenBack is displayed as the background of the view controller. If no image is placed in the image view, the view background image should just be nil.
choosenBack = UIImageView()
func call(){
self.view.backgroundColor == UIColor(patternImage: UIImage(choosenBack)!)
}
Using the backgroundColor property will only work when you indeed want the image to be repeated to fill the background. In that case you could simply do something like
func call() {
if let image = choosenBack.image {
self.view.backgroundColor = UIColor(patternImage: image)
} else {
self.view.backgroundColor = .white // Or w/e your default background is
}
}
If you want the background image to not repeat, you'll need to use a dedicated background image view.
let background = UIImageView()
override func viewDidLoad() {
super.viewDidLoad()
background.contentMode = .scaleAspectFit // Or w/e your desired content mode is
view.insertSubview(background, at: 0)
background.translatesAutoresizingMaskIntoConstraints = false
background.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
background.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true
background.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
background.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
...
}
func call() {
self.background.image = choosenBack.image
}

Masking A UIView With UILabel Using AutoLayout

I am working on a project that would result in this desired effect:
I am successfully drawing my circle using the following code:
let circle = CircleView()
circle.translatesAutoresizingMaskIntoConstraints = false
circle.backgroundColor = UIColor.clear
self.addSubview(circle)
// Setup constraints
circle.centerXAnchor.constraint(equalTo: self.centerXAnchor).isActive = true
circle.centerYAnchor.constraint(equalTo: self.centerYAnchor).isActive = true
circle.widthAnchor.constraint(equalTo: self.widthAnchor, multiplier: 0.6).isActive = true
circle.heightAnchor.constraint(equalTo: circle.widthAnchor, multiplier: 1.0/1.0).isActive = true
For reference, this is my CircleView class:
class CircleView: UIView {
override func draw(_ rect: CGRect) {
// Set the path
let path = UIBezierPath(ovalIn: rect)
// Set the fill color
UIColor.black.setFill()
// Fill
path.fill()
}
}
Subsequently, I am then creating my UILabel, as such:
let myLabel = UILabel()
myLabel.translatesAutoresizingMaskIntoConstraints = false
myLabel.text = "HELLO"
myLabel.textColor = UIColor.white
circle.addSubview(myLabel)
// Setup constraints
myLabel.centerXAnchor.constraint(equalTo: circle.centerXAnchor).isActive = true
myLabel.centerYAnchor.constraint(equalTo: circle.centerYAnchor).isActive = true
I have attempted to mask my circle by using:
circle.mask = myLabel
This, however, results in the opposite effect I am after (my text is not a cut-out in the UIView, but rather, my text is now black). Where might I be going wrong to accomplish this effect?
Masks work in an opposite fashion, when the color on your mask is not transparent then it will show the content in that pixels position and when the pixels are transparent then it will hide the content.
Since you can't achieve this using UILabel you need to use something else as a mask, such as a CALayer.
If you lookup "UILabel see through text" you should find results similar to this which basically also applies to you (with some changes).
Instantiate your CircleView, then instantiate a CATextLayer and use this as a mask for your UIView