Black to Clear Gradient Disappears when view is scrolled - ios

I have a CAGradientLayer with two colors, black and clear. When the view appears, the gradient displays appropriately however as soon as I scroll my CollectionView, the clear section of the gradient disappears.
let gradient: CAGradientLayer = {
let g = CAGradientLayer()
g.colors = [UIColor.black.cgColor, UIColor.clear.cgColor]
g.startPoint = CGPoint(x: 0.5, y: 1)
g.endPoint = CGPoint(x: 0.5, y: 0)
return g
}()
Below is the initial behavior.
Below is the behavior after scrolling.
Any suggestions?
EDIT
I have included some extra code which includes both the setup of the UIView and the addition of the gradient layer.
let toolBar: UIView = {
let n = UIView()
n.translatesAutoresizingMaskIntoConstraints = false
n.backgroundColor = UIColor.clear
return n
}()
view.addSubview(s.toolBar)
view.addConstraintsWithFormat("H:|[v0]|", views: s.toolBar)
s.toolBar.heightAnchor.constraint(equalToConstant: 50).isActive = true
s.toolBar.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
s.toolBar.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
s.toolBar.layer.insertSublayer(s.gradient, at: 0)
s.gradient.frame = CGRect(x: 0, y: 0, width: view.frame.width, height: 50)

The view was being created at override func viewWillLayoutSubviews()
instead of override func viewDidLoad() so whenever the view was scrolled another instance of the gradient was created for however many times viewWillLayoutSubviews was called resulting in multiple gradients being overlaid.

Related

UIButton loses responsivity(i.e can't press it), maybe because of CGAffineTransform

Firstly, I think that the problem is on the UIVisualEffectView, but now I think that the problem is on the CGAffineTransform that I'm using to animate the view on the openOptionsTooltip()
First, I found a similar problem on that question (see the comments on the accepted answer), but in my case I'm adding a button as subview of the UIVisualEffectView, not adding it as a button subview.
Apple documentation says:
Depending on the desired effect, the effect may affect content layered behind the view or content added to the visual effect view’s contentView.
But I can't find a better explanation on how or why it can affect the content added to the contentView.
I'm doing all the stuff programmatically, without any xib.
The visual effect view is a balloon like a tooltip that appears when I touch the titleView.
I'm creating that view on viewDidLoad and holding a
reference to it on the viewController.
After created, when I click on the titleView I'm adding it as a subview of UIWindow
Here's my code:
The method to create the view:
func createOptionsTooltip(withNamesAndActions options: [(name: String , action: Selector)]){
self.tooltipSize = CGSize(width: 250, height: CGFloat(60*options.count+12))
var optionItens : [UIView] = []
for i in 0..<options.count{
let optionView = UIView(frame: CGRect(x: .zero, y: CGFloat(60*i), width: self.tooltipSize.width, height: 60))
optionView.backgroundColor = .clear
let separatorView = UIView(frame: CGRect(x: .zero, y: 59, width: self.tooltipSize.width, height: 1))
separatorView.backgroundColor = UIColor(white: 0.8, alpha: 0.5)
let button = UIButton(frame: CGRect(x: .zero, y: .zero, width: self.tooltipSize.width, height: 59))
button.setTitle(options[i].name, for: .normal)
button.setTitleColor(self.view.tintColor, for: .normal)
button.addTarget(self, action: options[i].action, for: .touchUpInside)
button.isEnabled = true
button.isUserInteractionEnabled = true
optionView.addSubview(button)
optionView.addSubview(separatorView)
optionView.bringSubviewToFront(button)
optionItens.append(optionView)
}
self.blackOpaqueView = UIView(frame: self.view.bounds)
self.blackOpaqueView.backgroundColor = UIColor.black
self.blackOpaqueView.alpha = 0
let gesture = UITapGestureRecognizer(target: self, action: #selector(ScheduleNavigationController.closeOptionsTooltip))
// blackOpaqueView.addGestureRecognizer(gesture)
self.vwTooltip = UIView(frame: CGRect(origin: CGPoint(x: self.view.frame.midX - self.tooltipSize.width/2, y: self.view.frame.minY+self.navigationBar.frame.size.height+UIApplication.shared.statusBarFrame.height+10), size: CGSize(width: 1, height: 1)))
self.vwTooltip.transform = self.vwTooltip.transform.scaledBy(x: 1, y: 0.01)
self.vwTooltip.isHidden = true
let vwAim = UIVisualEffectView(frame: CGRect(x: self.tooltipSize.width/2 - 6, y: 0, width: 12, height: 10))
let vwBalloon = UIVisualEffectView(frame: CGRect(x: 0, y: vwAim.frame.size.height, width: self.tooltipSize.width, height: CGFloat(60*options.count)))
vwBalloon.cornerRadius = 8
vwBalloon.contentView.isUserInteractionEnabled = true
vwBalloon.isUserInteractionEnabled = true
let bezPath = trianglePathWithCenter(rect: vwAim.frame)
let maskForPath = CAShapeLayer()
maskForPath.path = bezPath.cgPath
vwAim.layer.mask = maskForPath
vwBalloon.effect = UIBlurEffect(style: .prominent)
vwAim.effect = UIBlurEffect(style: .prominent)
for optionView in optionItens{
vwBalloon.contentView.addSubview(optionView)
vwBalloon.bringSubviewToFront(optionView)
}
self.vwTooltip.addSubview(vwBalloon)
self.vwTooltip.addSubview(vwAim)
self.vwTooltip.bringSubviewToFront(vwBalloon)
vwBalloon.bringSubviewToFront(vwBalloon.contentView)
}
And I'm calling't on the viewDidLoad that way:
createOptionsTooltip(withNamesAndActions: [("Sair", #selector(ScheduleNavigationController.closeSchedule(_:))), ("Hoje", #selector(ScheduleNavigationController.scrollToToday(_:)))])
And I'm open the tooltip animated this way:
func openOptionsTooltip(){
if let appDelegate = UIApplication.shared.delegate as? AppDelegate{
appDelegate.window?.addSubview(blackOpaqueView)
appDelegate.window?.addSubview(vwTooltip)
appDelegate.window?.bringSubviewToFront(vwTooltip)
UIView.animate(withDuration: 0.2, animations: {
self.blackOpaqueView.alpha = 0.5
}) { _ in
self.vwTooltip.isHidden = false
UIView.animate(withDuration: 0.1, animations: {
self.vwTooltip.transform = self.vwTooltip.transform.scaledBy(x: 1, y: 100)
})
}
}
}
What I have tried:
Bring the buttons and all it's superview in hierarch to front using bringSubviewToFront. (I have checked, there's no view on the front of the button)
change the isUserInteractEnabled for all button superviews in hierarch.(for both false and true)
Change the way that I'm adding the target to buttons, without using the method parameter.
Change the selector method to any other method that work on other targets and switch the method between #objc and #IBOulet
Change the buttons to a view other than the UIVisualEffectView
PS: I found that the UIVisualEffectView has 3 subviews, but apparently the contentView is the above view, see the log of printing it's subviews:
▿ 3 elements
- 0 : <_UIVisualEffectBackdropView: 0x102dcd140; frame = (0 0; 250 120); autoresize = W+H; userInteractionEnabled = NO; layer = <UICABackdropLayer: 0x280d1b480>>
- 1 : <_UIVisualEffectSubview: 0x102dcd350; frame = (0 0; 250 120); autoresize = W+H; userInteractionEnabled = NO; layer = <CALayer: 0x280d15c80>>
- 2 : <_UIVisualEffectContentView: 0x102dcc940; frame = (0 0; 250 120); clipsToBounds = YES; autoresize = W+H; layer = <CALayer: 0x280d1ace0>>
I can't figure out what is blocking the button's user interaction
Here's the result:
I have found the problem:
After calling the openOptionsTooltip() the transform inside't are resizing all it's subview, but the vwTooltp itself don't change it's frames, but because it clipToBounds property are false, all the subviews are showing, but the user can't access the button actions because the view has 1x1 size. Changing the openOptionsTooltip() method to that:
func openOptionsTooltip(){
if let appDelegate = UIApplication.shared.delegate as? AppDelegate{
appDelegate.window?.addSubview(blackOpaqueView)
appDelegate.window?.addSubview(vwTooltip)
appDelegate.window?.bringSubviewToFront(vwTooltip)
UIView.animate(withDuration: 0.2, delay: 0.0, options: UIView.AnimationOptions.allowUserInteraction, animations:{
self.blackOpaqueView.alpha = 0.5
}) { _ in
self.vwTooltip.isHidden = false
UIView.animate(withDuration: 0.1, delay: 0.0, options: UIView.AnimationOptions.allowUserInteraction, animations:{
self.vwTooltip.transform = self.vwTooltip.transform.translatedBy(x: 1, y: 1)
self.vwTooltip.transform = self.vwTooltip.transform.scaledBy(x: 1, y: 100)
}){ _ in
self.vwTooltip.frame = self.tooltipFrame
}
}
}
}
And now it's working properly. Thanks for the responses

How can I add Cell index path as Index next to the Collectionview cell?

I have collection view (If the process is easier on a table view I can change it to that).
I need to display the index path of the Cell just before displaying the collection view cell.
I have been trying to figure out how to work on this, here are the things I worked out (Nothing has been a good solution).
Firstly, I tried using a Collection view cell with an added Label (For numbering) and UIView (For showing the content next to it). But the shadow code is not working for the UIView on the collection view cell.
#objc extension CALayer {
func applySketchShadow(
color: UIColor = .black,
alpha: Float = 0.5,
x: CGFloat = 0,
y: CGFloat = 2,
blur: CGFloat = 4,
spread: CGFloat = 0)
{
shadowColor = color.cgColor
shadowOpacity = alpha
shadowOffset = CGSize(width: x, height: y)
shadowRadius = blur / 2.0
if spread == 0 {
shadowPath = nil
} else {
let dx = -spread
let rect = bounds.insetBy(dx: dx, dy: dx)
shadowPath = UIBezierPath(rect: rect).cgPath
}
}
}
#objc extension UIView{
func applyShadowToView(){
self.layer.borderWidth = 1.0
self.layer.borderColor = UIColor.clear.cgColor
self.layer.masksToBounds = true
self.layer.masksToBounds = false
self.layer.applySketchShadow(color: UIColor.black, alpha: 0.09, x: 3, y: 2, blur: 50, spread: 4)
}}
For some weird reasons, my shadow view is not showing up. I tried various codes from online just to be sure, nothing works out.
Other things I thought off are having a header view to show the number of the Collection view, but header view needs to be as wide as Frame so this is not an option and
the other thing is having 2 collection view cells, even cells showing the Number and odd cells showing content.
But the problem with this is I want to add rearranging functionality later on and this method will not work when I want to do it.

Add UIView over UIViewImage. Users can zooming image

I tried to add UIView over my UIImage. But it doesn't work for me.
When I reduce and increase my image, UIView moved down and doesn't cover completely my image.
I use double click for zooming.
#objc func doubleTap(gestureRecognizer: UITapGestureRecognizer) {
if self.productScrollView.zoomScale == 1 {
self.productScrollView.zoom(to: self.zoomRectForScale(scale: self.productScrollView.maximumZoomScale, center: gestureRecognizer.location(in: gestureRecognizer.view)), animated: true)
} else {
self.productScrollView.setZoomScale(1, animated: true)
}
}
I tried to use these codes, but it doesn't work for me.
first:
let tintView = UIView()
tintView.backgroundColor = UIColor(white: 0, alpha: 0.5)
tintView.frame = CGRect(x: 0, y: 0, width: imageView.frame.width,
height: imageView.frame.height)
imageView.addSubview(tintView)
Second:
let tintView = UIView()
tintView.backgroundColor = UIColor(white: 0, alpha: 0.5)
tintView.translatesAutoresizingMaskIntoConstraints = false
imageView.addSubview(tintView)
NSLayoutConstraint.activate([
tintView.bottomAnchor.constraint(equalTo: imageView.bottomAnchor),
tintView.leadingAnchor.constraint(equalTo: imageView.leadingAnchor),
tintView.trailingAnchor.constraint(equalTo: imageView.trailingAnchor),
tintView.topAnchor.constraint(equalTo: imageView.topAnchor),
])
also, I tried to add on storyboard, but it also doesn't work for me.
Does anyone have ideas or suggestions?
Try to add your UIView to the main view of your viewController instead of UIImageView and set constraints to the imageView

Change width of UICollection view dynamically when x coordinates changes on TouchMoved function

everybody.
I am making a slide out menu in swift. I have made everything working fine. The slide out menu shows when hamburger button is clicked and also when right swipe on the screen is done. Now I want to make the size (width) of a slide out menu change based on the user coordinates on the screen.
The slide out is implemented using collection view on top of a UIView.
To take the user's position from the screen I have used touchMoved function to get user's real-time x value. Is there any method to use this value so that collection's view's width can be changed.
Any help will be very helpful. If needed I can upload all the codes. Changing the anchor size also didn't help.
MoreOptionsViewController.swift
override init() {
super.init();
collectionView.delegate = self;
collectionView.dataSource = self;
collectionView.register(MoreOptionsCell.self, forCellWithReuseIdentifier: cellId);
getData();
}
func showMoreOptions(){
if let window = UIApplication.shared.keyWindow{
window.addSubview(view);
view.setAnchor(top: window.topAnchor, bottom: window.bottomAnchor, left: window.leftAnchor, right: window.rightAnchor);
view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(handleTapOutside)));
view.alpha = 0.5;
collectionView.alpha = 1;
window.addSubview(collectionView);
let width = window.frame.width * 0.9;
//Animate the collection view's width
UIView.animate(withDuration: 0.5, delay: 0, usingSpringWithDamping: 1, initialSpringVelocity: 1, options: .curveEaseOut, animations: {
self.collectionView.leftAnchor.constraint(equalTo: window.leftAnchor).isActive = true;
self.collectionView.topAnchor.constraint(equalTo: window.topAnchor).isActive = true;
self.collectionView.widthAnchor.constraint(equalToConstant: width).isActive = true;
self.collectionView.heightAnchor.constraint(equalTo: window.heightAnchor).isActive = true;
}, completion: nil)
}
}
func handleTapOutside(){
collectionView.removeFromSuperview();
view.removeFromSuperview();
}
let view: UIView = {
let view = UIView();
view.backgroundColor = UIColor.black;
view.translatesAutoresizingMaskIntoConstraints = false;
return view;
}();
let collectionView: UICollectionView = {
let layout = UICollectionViewFlowLayout();
layout.minimumLineSpacing = 0;
let cv = UICollectionView(frame: .zero, collectionViewLayout: layout);
cv.showsVerticalScrollIndicator = false;
cv.backgroundColor = UIColor(red: 59/255, green: 65/255, blue: 65/255, alpha: 0.9);
cv.translatesAutoresizingMaskIntoConstraints = false;
return cv;
}();
The above code contains the actual collection view to present the slide out menu.
If i try to change the width from touchMoved function I get a lot of error. Any help will be much appreciated.
Try setting the collection view's width by updating its frame in your touchMoved function
yourCollectionView.frame = CGRect(x: 0, y: 0, width: <user's_x_scroll_value>, height: height)
Solved the problem. The problem was caused due to the animation code.
func showMoreOptions(xValue: CGFloat){
if let window = UIApplication.shared.keyWindow{
window.addSubview(view);
//Display the view beyond the screen. Increase the x coordinates according to the x value of screen
view.frame = CGRect(x: -window.frame.width + xValue , y: 0, width: window.frame.width, height: window.frame.height);
view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(handleTapOutside)));
print("View's x value is ", xValue)
view.alpha = 0.011; //Use minimum alpha value
collectionView.alpha = 1;
window.addSubview(collectionView);
//Same as above view
collectionView.frame = CGRect(x: -window.frame.width + xValue, y: 0, width: window.frame.width * 0.9, height: window.frame.height);
view.layoutIfNeeded()
collectionView.collectionViewLayout.invalidateLayout();
}
}
Also collectionView.collectionViewLayout.invalidateLayout() fixed the problem. Thank you Eli whittle for the help

Why is my table view shadow scrolling with my table view?

I added shadow to my table view but unfortunately when I scroll through the table view the shadow also moves with the table. The code for adding shadow is as follows:
func addShadow(to myView: UIView){
let shadowPath = UIBezierPath(rect: CGRect(x: 0, y: 0, width: uiView.frame.width, height: uiView.frame.height * 1.1))
uiView.layer.shadowColor = UIColor.black.cgColor
uiView.layer.shadowOffset = CGSize(width: 0.2, height: 0)
uiView.layer.shadowOpacity = 0.5
uiView.layer.shadowRadius = 5.0
uiView.layer.masksToBounds = false
uiView.layer.shadowPath = shadowPath.cgPath
}
Can you please explain to me why is this happening and how to make the shadow stick to its designated location?
Thank you in advance.
If you are adding shadow on tableView, it will scroll along with tableview data. To prevent that you have to add UIView first. Add tableview on that view. Add shadow for the UIView you have taken. It will stick to designated location.
This is my solution in Swift 3 with an UIView and a CAGradientLayer inside.
override func viewWillAppear(_ animated: Bool) {
addShadow(myView: myTab)
}
func addShadow(myView: UIView){
let shadowView = UIView()
shadowView.center = CGPoint(x: myView.frame.minX,y:myView.frame.minY - 15)
shadowView.frame.size = CGSize(width: myView.frame.width, height: 15)
let gradient = CAGradientLayer()
gradient.frame.size = shadowView.frame.size
let stopColor = UIColor.clear.cgColor
let startColor = UIColor.black.withAlphaComponent(0.8).cgColor
gradient.colors = [stopColor,startColor]
gradient.locations = [0.0,1.0]
shadowView.layer.addSublayer(gradient)
view.addSubview(shadowView)
}

Resources