Apply different custom UINavigationBar styles for multiple Views in SwiftUI - swift

I am currently struggling with defining multiple custom UINavigationBars for different Views in SwiftUI.
I have defined two different appearances in my UINavigationBar extension: a default one and one with a transparent background.
I am calling UINavigationBar.defaultNavigationBar() in my entry ContentView().
And when I'm navigating to another view via NavigationLink { ... }, I want to apply the different navigation bar appearances based on the logic in setNavigationBarStyle
The switch does not work and the UINavigationBar keeps its initial configuration which is defined in defaultNavigationBar()
So my question is, is this the right approach and I am missing some configurations? If not, what is the right approach to style the UINavigationBar differently based on my current view?
import SwiftUI
extension UINavigationBar {
static let appearance = UINavigationBarAppearance()
static func defaultNavigationBar() {
appearance.backgroundColor = UIColor(Color.audi.barBackground)
appearance.titleTextAttributes = [
NSAttributedString.Key.foregroundColor: UIColor(Color.audi.barContent),
NSAttributedString.Key.font: UIFont(name: Fonts.typeVFNormalBold.rawValue, size: 12)!
]
appearance.shadowColor = UIColor(Color.audi.barTopSeparator)
appearance.setBackIndicatorImage(UIImage(named: "back-small")!, transitionMaskImage: UIImage(named: "back-small")!)
UINavigationBar.appearance().standardAppearance = appearance
UINavigationBar.appearance().compactAppearance = appearance
UINavigationBar.appearance().scrollEdgeAppearance = appearance
}
static func transparentNavigationBar() {
appearance.configureWithTransparentBackground()
appearance.titleTextAttributes = [
NSAttributedString.Key.foregroundColor: UIColor(Color.audi.barContentOverlay),
NSAttributedString.Key.font: UIFont(name: Fonts.typeVFNormalBold.rawValue, size: 12)!
]
appearance.shadowColor = .clear
appearance.setBackIndicatorImage(UIImage(named: "back-white-small")!, transitionMaskImage: UIImage(named: "back-white-small")!)
UINavigationBar.appearance().standardAppearance = appearance
UINavigationBar.appearance().compactAppearance = appearance
UINavigationBar.appearance().scrollEdgeAppearance = appearance
}
}
func setNavigationBarStyle(scrollY: CGFloat) {
if scrollY <= 0 {
UINavigationBar.transparentNavigationBar()
} else {
UINavigationBar.defaultNavigationBar()
}
}

Related

Remove Gradient and make Transparent navigation bar in ios 15

I have tried the following code to remove the gradient from navigation and make a transparent navigation bar for ios 15, But it didn't work on the same whereas in the lower version it works perfectly fine.
if let bar = self.navigationController?.navigationBar {
if #available(iOS 15.0, *) {
let navigationBarAppearance = UINavigationBarAppearance()
navigationBarAppearance.configureWithTransparentBackground()
navigationBarAppearance.shadowImage = UIImage()
self.title = ""
navigationBarAppearance.backgroundImage = UIImage()
navigationBarAppearance.backgroundColor = .clear
UINavigationBar.appearance().isTranslucent = true
UINavigationBar.appearance().standardAppearance = navigationBarAppearance
UINavigationBar.appearance().scrollEdgeAppearance = navigationBarAppearance
} else {
bar.setBackgroundImage(FCUtil.getImageWithColor(color: UIColor.clear, size: bar.frame.size), for: UIBarMetrics.default)
bar.isTranslucent = true
bar.shadowImage = UIImage()
self.title = ""
}
}
Please suggest to me, If I had missed something.
The only problem with your code is the timing. You are running the code too late. Changing the navigation bar appearance() cannot change a navigation bar that already exists.
Instead, just change bar itself, directly:
bar.standardAppearance = navigationBarAppearance
bar.scrollEdgeAppearance = navigationBarAppearance

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.

Label and NSView in a vibrant background

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

tvOS: Anyway to display a subtitle outside of the AVPlayer?

So the scenario is that there is a view where the user can enable/disable subtitles in an app I'm helping to develop.
On that view there is a sample text saying "This is what captions look like", and at the moment it's just a basic, unstyled UILabel. Ideally I would like it to be styled in a similar manner to how the user has customized their captions in the System Settings.
Is this possible in any way? I've envisioned two possible method:
Create an AVPlayer instance and a .vtt file with the text, load it into the view and pause the player. I'm not sure this is possible with a sample video (and it would somehow have to be transparent as there is an image behind the sample sub text).
Somehow get all the styling (font, size, background color, etc) the user has set for their subtitle and create an attributed string to match that
Method 2 seems like the most feasible way, but I don't know if we have access to those settings in code.
So I figured it out! It basically makes use a combination of the Media Accessibility API, which allows you to get the values the user has chosen for their captions/subtitle settings, Attributed Strings, and a subclass UILabel (although this could maybe be substituted with a UITextView as that will allow you to set it's UIEdgeInsets natively)
So, first, the subclass is to allow the UILabel to be inset. This is because captions can have a background color AND a text highlight color and without the inset, the text highlight is all you see. So the function the subclass is simple:
class InsetUILabel: UILabel {
override func drawTextInRect(rect: CGRect) {
let inset: CGFloat = 15
let insets: UIEdgeInsets = UIEdgeInsets(top: inset, left: inset/2, bottom: inset, right: inset/2)
super.drawTextInRect(UIEdgeInsetsInsetRect(rect, insets))
}
}
And for generating the actual label. This uses a label called textSample, but you can obviously make it a little more general.
import MediaAccessibility
func styleLabel(sampleText: String) {
let domain = MACaptionAppearanceDomain.User
// Background styling
let backgroundColor = UIColor(CGColor: MACaptionAppearanceCopyWindowColor(domain, nil).takeRetainedValue())
let backgroundOpacity = MACaptionAppearanceGetWindowOpacity(domain, nil)
textSample.layer.backgroundColor = backgroundColor.colorWithAlphaComponent(backgroundOpacity).CGColor
textSample.layer.cornerRadius = MACaptionAppearanceGetWindowRoundedCornerRadius(domain, nil)
// Text styling
var textAttributes = [String:AnyObject]()
let fontDescriptor = MACaptionAppearanceCopyFontDescriptorForStyle(domain, nil, MACaptionAppearanceFontStyle.Default).takeRetainedValue()
let fontName = CTFontDescriptorCopyAttribute(fontDescriptor, "NSFontNameAttribute") as! String
let fontColor = UIColor(CGColor: MACaptionAppearanceCopyForegroundColor(domain, nil).takeRetainedValue())
let fontOpacity = MACaptionAppearanceGetForegroundOpacity(domain, nil)
let textEdgeStyle = MACaptionAppearanceGetTextEdgeStyle(domain, nil)
let textHighlightColor = UIColor(CGColor: MACaptionAppearanceCopyBackgroundColor(domain, nil).takeRetainedValue())
let textHighlightOpacity = MACaptionAppearanceGetBackgroundOpacity(domain, nil)
let textEdgeShadow = NSShadow()
textEdgeShadow.shadowColor = UIColor.blackColor()
let shortShadowOffset: CGFloat = 1.5
let shadowOffset: CGFloat = 3.5
switch(textEdgeStyle) {
case .None:
textEdgeShadow.shadowColor = UIColor.clearColor()
case .DropShadow:
textEdgeShadow.shadowOffset = CGSize(width: -shortShadowOffset, height: shortShadowOffset)
textEdgeShadow.shadowBlurRadius = 6
case .Raised:
textEdgeShadow.shadowOffset = CGSize(width: 0, height: shadowOffset)
textEdgeShadow.shadowBlurRadius = 5
case .Depressed:
textEdgeShadow.shadowOffset = CGSize(width: 0, height: -shadowOffset)
textEdgeShadow.shadowBlurRadius = 5
case .Uniform:
textEdgeShadow.shadowColor = UIColor.clearColor()
textAttributes[NSStrokeColorAttributeName] = UIColor.blackColor()
textAttributes[NSStrokeWidthAttributeName] = -2.0
default:
break
}
textAttributes[NSFontAttributeName] = UIFont(name: fontName, size: (textSample.font?.pointSize)!)
textAttributes[NSForegroundColorAttributeName] = fontColor.colorWithAlphaComponent(fontOpacity)
textAttributes[NSShadowAttributeName] = textEdgeShadow
textAttributes[NSBackgroundColorAttributeName] = textHighlightColor.colorWithAlphaComponent(textHighlightOpacity)
textSample.attributedText = NSAttributedString(string: sampleText, attributes: textAttributes)
}
Now the text highlight section makes use of shadows, with values I think look pretty good, but you might want to tweak them a tiny bit. Hope this helps!

Why does .textAlligment only works if its placed after .defaultTextAttribute?

Why does text alignment property does not work if its declared before .defaultTextAttribute bunch of code? It didn't throw any run-time errors but its property never changed or did any effect.
I have the following code working:
override func viewDidLoad() {
super.viewDidLoad()
// Setting the delegates
self.upperTextField.delegate = self
self.lowerTextField.delegate = self
// Default text of textFields
upperTextField.text = "Up"
lowerTextField.text = "Down"
// Default text Attributes
let textAttributes = [
NSStrokeColorAttributeName: UIColor.whiteColor(),
NSForegroundColorAttributeName: UIColor.blackColor(),
NSFontAttributeName: UIFont(name: "HelveticaNeue-CondensedBlack", size: 40)!,
NSStrokeWidthAttributeName: 0
]
upperTextField.defaultTextAttributes = textAttributes
lowerTextField.defaultTextAttributes = textAttributes
// Align text
upperTextField.textAlignment = .Center
lowerTextField.textAlignment = .Center
}
I got it working when I changed
// Align text
upperTextField.textAlignment = .Center
lowerTextField.textAlignment = .Center
after // Default text attributes
Im using Xcode 7.3 and swift 2.
Thanks in advance for any suggestions!