Remove BackgroundView from UITargetedPreview in Swift - swift

I'm trying to remove the background view for my UITargetPreview. I made the background color clear, however, you can still see the frame of the background.
This is what it currently looks like:
I currently have a view that has the text container and the image inside of it and that's what I use as the view for the UITargetedPreview.
Is there a way to only show the image and the text and not the background frame?

There is a tricky method to hide the shadow and to do that you should find a view with _UIPlatterSoftShadowView class name in the view hierarchy and then hide it.
func viewByClassName(view: UIView, className: String) -> UIView? {
let name = NSStringFromClass(type(of: view))
if name == className {
return view
}
else {
for subview in view.subviews {
if let view = viewByClassName(view: subview, className: className) {
return view
}
}
}
return nil
}
override func tableView(_ tableView: UITableView, willDisplayContextMenu configuration: UIContextMenuConfiguration, animator: UIContextMenuInteractionAnimating?) {
DispatchQueue.main.async {
if let window = UIApplication.shared.delegate?.window! {
if let view = self.viewByClassName(view: window, className: "_UIPlatterSoftShadowView") {
view.isHidden = true
}
}
}
}
NOTE: It's not documented internal class and can be changed anytime further but it works now on both ios 13/14.

Have you tried subclassing the UIView as a UIControl?
I had a similar issue but in my case the view for UITargetedPreview was glitchy. However, changing the UIView to a UIControl fixed everything.

try removing shadow of that background view.

You need to study UIBezierPath() to outline the specific area you want to enclose before you present the target view.
After that, you shall assign the specific path to shadow path / visible path
let params = UIPreviewParameters()
params.backgroundColor = .clear
if #available(iOS 14.0, *) {
params.shadowPath = bubblePath
} else {
params.visiblePath = bubblePath
}

Related

How to make a NSTextView, that is added programmatically, active?

I am making an app where a user can click anywhere on the window and a NSTextView is added at the mouse location. I have got it working with the below code but I am not able to make it active (in focus) after adding it to the view (parent view). I have to click on the NSTextView to make it active but this is not what I want. I want it to automatically become active when its added to the parent view.
Code in my ViewController to add the NSTextView to its view:
private func addText(at point: NSPoint) {
let textView = MyTextView(frame: NSRect(origin: point, size: CGSize(width: 150.0, height: 40.0)))
view.addSubview(textView)
}
MyTextView class looks like below:
class MyTextView: NSTextView {
override var shouldDrawInsertionPoint: Bool {
true
}
override var canBecomeKeyView: Bool {
true
}
override func viewWillDraw() {
isHorizontallyResizable = true
isVerticallyResizable = true
insertionPointColor = .red
drawsBackground = false
isRichText = false
allowsUndo = true
font = NSFont.systemFont(ofSize: 40.0)
}
}
Also, I want it to lose focus (become inactive) when some other elements (view) are clicked. Right now, once a NSTextView becomes active, it stays active no matter what other elements I click except when I click on an empty space to create yet another NSTextView.
I have gone through the Apple docs multiple times but I think I am missing something. Any help would be much appreciated.
Get the NSWindow instance of the NSViewController's view and call makeFirstResponder passing the text view as parameter.
To lose focus call makeFirstResponder passing nil.

Detect if label touches navigation bar

I am trying to make view that is scrollable. That viewcontroller contains also navbar. Now my goal is to resize my view if the title in that view touches the navbar. How should I do it?
This is how my view looks like(note that the navBar is just transparent):
What I want after the title collision:
I know that I can achieve it in scrollViewDidScroll delegate function, but how?
Well you can track label position using convertRect: method as
func scrollViewDidScroll(_ scrollView: UIScrollView) {
let labelTop = label.rectCorrespondingToWindow.minY
let navBottom = self.navigationController?.navigationBar.rectCorrespondingToWindow.maxY
if navBottom == labelTop {
// do what you want to do
}
}
extension UIView{
var rectCorrespondingToWindow:CGRect{
return self.convert(self.bounds, to: nil)
}
}

How can I use NSVisualEffectView to blend window with background

There seem to be a bunch of questions on this for old versions of Swift/Xcode, but for some reason it hasn't been working with the latest update. I created a NSVisualEffectView, blurryView, and added the subview to my main view:
class ViewController: NSViewController {
#IBOutlet weak var blurryView: NSVisualEffectView!
override func viewDidLoad() {
super.viewDidLoad()
//background styling
blurryView.wantsLayer = true
blurryView.blendingMode = NSVisualEffectBlendingMode.behindWindow
blurryView.material = NSVisualEffectMaterial.dark
blurryView.state = NSVisualEffectState.active
self.view.addSubview(blurryView, positioned: NSWindowOrderingMode.above, relativeTo: nil)
// Do any additional setup after loading the view.
}
...
}
But when I run it, there is no effect on the window. (when I set it to within window, and layer it on top of my other view, the blur works correctly, but I only want the window to blur.) I also tried doing the same thing in my App Delegate class, but I can't connect my window as an outlet, and therefore can't add the blurry view to the window. Here's what the code would look like:
class AppDelegate: NSObject, NSApplicationDelegate {
func applicationDidFinishLaunching(_ aNotification: Notification) {
// Insert code here to initialize your application
blurryView.wantsLayer = true
blurryView.blendingMode = NSVisualEffectBlendingMode.withinWindow
blurryView.material = NSVisualEffectMaterial.dark
blurryView.state = NSVisualEffectState.active
self.window.contentView?.addSubview(blurryView)
}
...
}
To get an idea if what I'm looking for: NSVisualEffectView Vibrancy
It works quite easy:
In Interface Builder drag a NSVisualEffectView directly as a subview of the main view of your scene.
In the Properties Inspector set Blending Mode to Behind Window
Add the rest of the views you need as subviews of the NSVisualEffectView
That's it, you're done
Here's an example:
Panel 1 View Controller is my blurred view, Background View is the first (non-blurred) view in my "real"view hierarchy.
Swift 5:
Simply add this to your viewWillAppear and it should work:
override func viewWillAppear() {
super.viewWillAppear()
//Adds transparency to the app
view.window?.isOpaque = false
view.window?.alphaValue = 0.98 //you can remove this line but it adds a nice effect to it
let blurView = NSVisualEffectView(frame: view.bounds)
blurView.blendingMode = .behindWindow
blurView.material = .fullScreenUI
blurView.state = .active
view.window?.contentView?.addSubview(blurView)
}

How to slide TabBar up to hide it in tvOS app?

In my tvOS app I have a TabBarController with 3 viewControllers. What I want to do is to automatically hide/change focus of the tabBar when I switch to the next viewController.
I saw some posts here, on SO that suggested to change alfa on the tabBar, but I would like to have a slide up animation, same way as it does when you change focus to something in the viewController.
Any kind of help is highly appreciated.
As Charles said.. Something like this in the derived UITabBarController:
var focusOnChildVC : Bool = false {
didSet {
self.setNeedsFocusUpdate()
}
};
override weak var preferredFocusedView: UIView? {
get {
let v : UIView?;
let focused = UIScreen.mainScreen().focusedView
//A bit of a hack but seems to work for picking up whether the VC is active or not
if (focusOnChildVC && focused != nil) {
v = self.selectedViewController?.preferredFocusedView
} else {
//If we are focused on the main VC and then clear out of property as we're done with overriding the focus now
if (focusOnChildVC) {
focusOnChildVC = false
}
v = super.preferredFocusedView;
}
return v
}
}
The basic idea of the solution described below is to subclass UITabBarController and selectively use the super implementation of weak var preferredFocusedView: UIView? { get } or one that returns selectedViewController?.preferredFocusView along with an implementation of didUpdateFocusInContext(_:withAnimationCoordinator:) that sets up an NSTimer that triggers a focus update and sets a flag that controls the preferredFocusView implementation.
More verbosely, Subclass UITabBarController and override didUpdateFocusInContext(context: UIFocusUpdateContext, withAnimationCoordinator coordinator: UIFocusAnimationCoordinator). In your implementation (make sure to call the super implementation) you can inspect the context and determine if a descendent view of the tabBar property is the nextFocusedView or the previousFocusedView (and the nextFocusedView is not a descendent).
If the tab bar is gaining focus you can create an NSTimer for the duration that you want to show the tab bar before hiding it. If the tab bar loses focus before the timer fires, invalidate it. If the timer fires, call setNeedsFocusUpdate() followed by updateFocusIfNeeded().
The last piece you need to get this to work is a flag that is set to true while the timer is set. You then need to override weak var preferredFocusedView: UIView? { get } and call the super implementation if the flag is false and if it is true return selectedViewController?.preferredFocusedView.
You can do it in a UITabBarController subclass:
final class TabBarViewController: UITabBarController {
private(set) var isTabBarHidden = false
func setTabBarHidden(_ isHidden: Bool, animated: Bool) {
guard isTabBarHidden != isHidden else {
return
}
var frame: CGRect
let alpha: CGFloat
if isHidden {
frame = tabBar.frame
frame.origin.y -= frame.height
alpha = 0
} else {
frame = tabBar.frame
frame.origin.y += frame.height
alpha = 1
}
let animations = {
self.tabBar.frame = frame
self.tabBar.alpha = alpha
}
if animated {
UIView.animate(withDuration: 0.3, animations: animations)
} else {
animations()
}
isTabBarHidden = isHidden
}
}

Remove blur effect in tableview cell in Swift

I make this in my IF clausure, in the cellForRowAtIndexPath method:
let blurEffect = UIBlurEffect(style: .Light)
let visualEffect = UIVisualEffectView(effect: blurEffect)
visualEffect.frame = cell.attachedImage.bounds
cell.attachedImage.addSubview(visualEffect)
In my ELSE clausure I want to remove the effect from cell.attachedImage.
How can I do it?
cellForRowAtIndexPath should not be adding subviews to cells. It's problematic because cells get reused, and if you're not keeping references to what you've added, you'll end up adding views multiple times and that can cause scrolling speed problems, as well as other bugs.
The proper approach is to create a cell subclass that already has the views set up. Your subclass can contain properties that reference the views so you can change, hide, move, or remove them from their superview as needed. But the logic for this should be in the cell subclass.
You just need to remove the visual effect view from it's superview. The trick is finding the right view to remove when the cell is recycled. The easiest way to do that would be to store it in a custom UITableViewCell implementation:
class CustomTableViewCell: UITableViewCell {
var visualEffectView: UIView?
var attachedImage: UIImageView!
// other views, including outlets
func setupIsBlurred(isBlurred: Bool) {
if isBlurred {
if self.visualEffectView == nil {
let blurEffect = UIBlurEffect(style: .Light)
let visualEffectView = UIVisualEffectView(effect: blurEffect)
visualEffectView.frame = self.attachedImage.bounds
self.attachedImage.addSubview(visualEffectView)
self.visualEffectView = visualEffectView
}
} else {
self.visualEffectView?.removeFromSuperview()
self.visualEffectView = nil
}
}
}