Side of TableViewCell gets cut off when applying a gradient mask - ios

My tableView sits within a blank content view with constraints holding it in place. The table will function properly in the container by itself, but as soon as I apply a gradient using the code below, the gradient works perfectly, except the right hand side of my tableViewCells get cut off. It only happens when the gradient is applied. Any recommendations would be appreciated.
Before Gradient is added, table view looks like this:
TableView with full TableViewCells
After the gradient is added, the table view looks like this:
TableView with cut TableViewCells
This is the code I am using to apply the gradient.
let gradientLayerMask = CAGradientLayer()
gradientLayerMask.frame = gradientMask.bounds
gradientLayerMask.colors = [UIColor.clear.cgColor, UIColor.white.cgColor, UIColor.white.cgColor, UIColor.clear.cgColor]
gradientLayerMask.locations = [0.0,0.1,0.9,1.0]
gradientMask.layer.mask = gradientLayerMask
exerciseTable.contentInset = UIEdgeInsets(top: 15, left: 0, bottom: 0, right: 0)

Put some delay for gradient code
func delay(time: Double, closure:
#escaping ()->()) {
DispatchQueue.main.asyncAfter(deadline: .now() + time) {
closure()
}
}
Use this way.
delay(time: 0.2) {
//gradient code
}
I hope it's work for you.

Related

Swift UITableViewCell Shadow not appearing

I am trying to create custom table view cell which works fine in my other UIViewControllers. However, in one of my controllers, the shadow is not growing, I can barely see the shadow.
Here is an image of the shadow being shown in red, you can see it is barely visible.
My cell has a UIView added inside the contentView to creating floating cell effects - the same code and same storyboard layouts are being used across my controllers but this is the only table view where the shadow issue is occurring - so I must be missing something.
My addShadow extension:
extension UIView {
func addShadow(offset: CGSize, color: UIColor, radius: CGFloat, opacity: Float) {
layer.masksToBounds = false
layer.shadowOffset = offset
layer.shadowColor = color.cgColor
layer.shadowRadius = radius
layer.shadowOpacity = opacity
}
}
My awakeFromNib on the custom cell:
:: cellContentView is my UIView added to the base contentView of the cell.
override func awakeFromNib() {
super.awakeFromNib()
self.backgroundColor = .clear
self.selectionStyle = .none
cellContentView?.layer.masksToBounds = true
cellContentView?.round(corners: [.topLeft, .topRight, .bottomLeft, .bottomRight], radius: 10)
cellContentView?.addShadow(offset: CGSize(width: 40, height: 60), color: UIColor.red, radius: 10, opacity: 1)
cellContentView?.layer.shouldRasterize = true
}
Note: The .round is an extension being used on all my cells.
No matter what radius or offset I add for this shadow, it does not get bigger than the image. Also, none of my other cells in the their controllers require the shouldRasterize property to be set, but this does.
Does anyone know what is happening here?
Thanks :)
Edit
Strangely, if I add constraints around my view to keep the gaps large between my view and the cell content view, the background colour disappears - this is set to white in the storyboard.
You should call in the layoutSubviews method. because shadow should add after the view is uploaded
override func awakeFromNib() {
super.awakeFromNib()
//init methods
}
override public func layoutSubviews() {
super.layoutSubviews()
//Added shadow
self.reloadLayers()
}
private func reloadLayers() {
self.layer.cornerRadius = 5
self.addShadow(.TransactionCell)
}
I hope it helps
Content view will fill you cell, so you need to add shadow to view inside content view which has all your components inside it. Then add constraints to it with gap between that view and content view. Second, 40 and 60 properties for shadow is likely too large, when I said too large I mean unbelievable large, because gap between content views in cells are no more than 15 - 30 even less. so try it with much less values, while radius can remain 10 but you will see what value fit the best. If cell content view is your custom view just values will did the job if your view is not kind of transparent or any inside it, in that case it won't, and there is hard to fix that, I tried many libraries and custom codes and it is never ok.
squircleView.layer.cornerRadius = 40
squircleView.layer.cornerCurve = CALayerCornerCurve.continuous
squircleView.layer.shadowColor = UIColor.systemGray.cgColor
squircleView.layer.shadowOpacity = 0.7
squircleView.layer.shadowOffset = CGSize(width: 0, height: 0.5)
squircleView.layer.shadowRadius = 5

Center of button in scrollview

I wanted to make an animation where a pulse is being created when you hit a button. But that doesn't really work on a scrollview because it turns out to be a specific point on the 'screen', not on the scrollview.
When you're scrolling down, the origin of the pulse stays the same.
#objc func addPulse() {
let pulse = Pulsing(numberOfPulses: 1, radius: 120, position: playButton.center)
pulse.animationDuration = 0.8
pulse.backgroundColor = UIColor.red.cgColor
self.view.layer.insertSublayer(pulse, below: playButton.layer)
The position has to be from the type CGPoint.
If you are using a ScrollView, better use the center of the scrollview instead of the center of the button as the button will disappear as you scroll. So when you create the Pulsing object , use the self.view.center as position.
#objc func addPulse() {
let pulse = Pulsing(numberOfPulses: 1, radius: 120, position: self.view.center)
pulse.animationDuration = 0.8
pulse.backgroundColor = UIColor.red.cgColor
self.view.layer.insertSublayer(pulse, below: playButton.layer)
}
By the little amount of context you gave, I am gonna go ahead and assume you have a button inside a UIScrollView and you need to add a pulse animation behind the button but the problem is that as the view scrolls and the button changes its position the animation keeps appearing at the same position. Also, I suppose you're inside an UIViewController class.
If I'm completely right with my guesses this is what I think it's happening:
The position of playButton will always be the same since it is its position against its parent, in this case, the UIScrollView. The absolute position would be the position of the item in the screen.
You are adding the animation inside of the UIView (of the UIViewController) instead of the UIScrollView itself. This will be a problem while scrolling.
My suggestion is for you to try:
#objc func addPulse() {
let pulse = Pulsing(numberOfPulses: 1, radius: 120, position: playButton.center)
pulse.animationDuration = 0.8
pulse.backgroundColor = UIColor.red.cgColor
scrollView.layer.insertSublayer(pulse, below: playButton.layer)
}

Ever scrolling UIScrollView with UIImage

I have a horizontal UIScrollview in my app which has 1 really long UIImageView to start with. I have a timer and animation to create an illusion that the image under scroll view is automatically scrolling. Once the image comes to an end i dynamically add similar image to the scroll view so it should look like the image is repeating itself.
This is how i want them to be displayed under scroll view : image1|image2|image3|image4...... and these images will be scrolling from right to left. Exactly how it works in Behance's iphone app before you login.
Here's the code i have (in storyboard i have the scroll view and one UIIMageview already added).
override func viewDidAppear(_ animated: Bool) {
Timer.scheduledTimer(timeInterval: 0.6, target: self, selector: #selector(scrollImage), userInfo: nil, repeats: true)
}
#objc func scrollImage() {
offSet.x = offSet.x + CGFloat(20)
UIView.animate(withDuration: 1.0, animations: {
self.behanceView.setContentOffset(self.offSet, animated: false)
})
}
func addImagetoScrollView() {
let imageView = UIImageView(image:UIImage(named:"Landing_Scrollable"))
print(imageCount*Int(self.behanceView.contentSize.width)+100)
imageView.frame = CGRect(x:imageCount*Int(self.behanceView.contentSize.width), y: 0, width: 875, height: 502)
self.behanceView.contentSize = CGSize(width: imageView.bounds.size.width * CGFloat(imageCount), height: imageView.bounds.size.height)
self.behanceView.addSubview(imageView)
imageCount+=1
}
extension ViewController: UIScrollViewDelegate {
func scrollViewDidScroll(_ scrollView: UIScrollView) {
let scrollViewWidth = scrollView.frame.size.width;
let scrollOffset = scrollView.contentOffset.x;
print(imageCount*Int(self.behanceView.contentSize.width - scrollViewWidth))
if scrollOffset >= CGFloat(imageCount*Int(self.behanceView.contentSize.width - scrollViewWidth)) {
self.addImagetoScrollView()
}
}
}
But when i see it in action, it does something wierd and animation is all off.
Can someone please help.
Thanks,
I've never seen the “Behance” app, but I guess you're asking how to animate a seamlessly tiled background image across the screen indefinitely, like this:
(Pattern image by Evan Eckard.)
I used an animation duration of 1 second for the demo, but you probably want a much longer duration in a real app.
You shouldn't use a timer for this. Core Animation can perform the animation for you, and letting it perform the animation smoother and more efficient. (You might think Core Animation is performing your animation since you're using UIView animation, but I believe animating a scroll view's contentOffset does not use Core Animation because the scroll view has to call its delegate's scrollViewDidScroll on every animation step.)
You also shouldn't use a scroll view for this. UIScrollView exists to allow the user to scroll. Since you're not letting the user scroll, you shouldn't use UIScrollView.
Here's how you should set up your background:
Create two identical image views (numbered 0 and 1), showing the same image. Make sure the image views are each big enough to fill the screen.
Put the left edge of image view 0 at the left edge of your root view. Put the left edge of image view 1 at the right edge of image view 0. Since each image view is big enough to fill the screen, image view 1 will start out entirely off the right edge of the screen.
Animate image view 0's transform.translation.x from 0 to -imageView.bounds.size.width. This will make it slide to the left by precisely its own width, so when the animation reaches its end, image view 0's right edge is at the left edge of the screen (and thus image view 0 is entirely off the left edge of the screen). Set the animation's repeatCount to .infinity.
Add the same animation to image view 1. Thus image view 1 comes onto the screen as image view 0 is leaving it, exactly covering the pixels revealed by image view 0's animation.
The two animations end at exactly the same time. When they end, image view 1 is exactly where image view 0 was at the start. Since both animations are set to repeat infinitely, they both immediately start over. When image view 0's animation starts over, image view 0 instantly jumps back to its starting position, which is where image view 1 ended up. Since both image views show the same image, the pixels on screen don't change. This makes the animation loop seamless.
Here's my code:
import UIKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
for imageView in imageViews {
imageView.image = patternImage
imageView.contentMode = .scaleToFill
view.addSubview(imageView)
}
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
let bounds = view.bounds
let patternSize = patternImage.size
// Scale the image up if necessary to be at least as big as the screen on both axes.
// But make sure scale is at least 1 so I don't shrink the image if it's larger than the screen.
let scale = max(1 as CGFloat, bounds.size.width / patternSize.width, bounds.size.height / patternSize.height)
let imageFrame = CGRect(x: 0, y: 0, width: scale * patternSize.width, height: scale * patternSize.height)
for (i, imageView) in imageViews.enumerated() {
imageView.frame = imageFrame.offsetBy(dx: CGFloat(i) * imageFrame.size.width, dy: 0)
let animation = CABasicAnimation(keyPath: "transform.translation.x")
animation.fromValue = 0
animation.toValue = -imageFrame.size.width
animation.duration = 1
animation.repeatCount = .infinity
animation.timingFunction = CAMediaTimingFunction(name: .linear)
// The following line prevents iOS from removing the animation when the app goes to the background.
animation.isRemovedOnCompletion = false
imageView.layer.add(animation, forKey: animation.keyPath)
}
}
private let imageViews: [UIImageView] = [.init(), .init()]
private let patternImage = #imageLiteral(resourceName: "pattern")
}

Gradient at Top/Bot of Scrollview When There's Additional Content

I have a scrollview>ContentView>TextLabel setup where the gradient is showing up but not working how I want it to. It's a clear background scrollView, so all I'm looking for is a clear gradient mask over the text. I found something similar to I'm looking for in Objective-C but that thread doesn't have a Swift solution.
My end goal seems like something most people might use, so I'm surprised there's not a Swift version yet. The functionality I'm trying to code is:
Sometimes the text will fit perfectly in the scrollView's fixed size so there should be no gradient.
When the text is longer than can fit and so some of it is below the scrollView's bottom cutoff, the bottom of the view should fade to clear.
Once the user scrolls down and the top should fade, indicating that there's more above.
I tried this code to handle bullet #2:
let gradient = CAGradientLayer()
gradient.frame = self.bio_ScrollView.superview!.bounds ?? CGRectNull
gradient.colors = [UIColor.whiteColor().CGColor, UIColor.clearColor().CGColor]
//gradient.locations = [0, 0.15, 0.25, 0.75, 0.85, 1.0]
gradient.locations = [0.6, 1.0]
self.bio_ScrollView.superview!.layer.mask = gradient
But it fades everything, including the button below it, which is clearly not in the scrollview:
If I remove the .superview and apply it directly to the scrollView, it just fades all the text below the initial part that was visible:
Does anyone know anything about how to implement this correctly?
Figured it out. First, make sure you've added the right view hierarchy. The scroll view needs to be embedded in a container view (which is what the gradient will be applied to):
Top/container view: Set this up however you want
Scrollview: Pin all edges to container (4 total constraints)
Scrollview content view: Pin edges + Center X (5 total constraints)
Label: Pin edges (4 total constraints)
Add "scrollViewDelegate" to the ViewController class:
class ViewController_WithScrollView: UIViewController, UIScrollViewDelegate {
....
Connect the four views listed above with IBOutlets. Then, set your scrollView delegate within the viewDidLoad:
override func viewDidLoad() {
super.viewDidLoad()
yourScrollView.delegate = self
//+All your other viewDidLoad stuff
}
Then implement the scrollViewDidScroll func, which will run automatically thanks to the work you did above.
func scrollViewDidScroll (scrollView: UIScrollView) {
if scrollView.isAtTop {
self.applyGradient_To("Bot")
} else if scrollView.isAtBottom {
self.applyGradient_To("Top")
} else {
self.applyGradient_To("Both")
}
}
Then add this magical gradient code:
func applyGradient_To (state: String) {
let gradient = CAGradientLayer()
gradient.frame = self.yourScrollView.superview!.bounds ?? CGRectNull
switch state {
case "Top":
gradient.colors = [UIColor.clearColor().CGColor,UIColor.whiteColor().CGColor]
gradient.locations = [0.0,0.2]
case "Bot":
gradient.colors = [UIColor.whiteColor().CGColor, UIColor.clearColor().CGColor]
gradient.locations = [0.8,1.0]
default:
gradient.colors = [UIColor.clearColor().CGColor,UIColor.whiteColor().CGColor,UIColor.whiteColor().CGColor, UIColor.clearColor().CGColor]
gradient.locations = [0.0,0.2,0.8,1.0]
}
self.yourScrollView.superview!.layer.mask = nil
self.yourScrollView.superview!.layer.mask = gradient
}
That should do it!

Adding gradient to scrolling UITableView

I was able to add a gradient my UITableView, but I have the issue of when I have to scroll through my cells, the gradient background scrolls along also. I want the background to stay consistent as I scroll up or down. How can I achieve this? Do I have to create a custom UITableView in order to do this?
The pictures below show what it currently looks like.
Here is my code for adding the gradient to the UITableView:
func addGradientToBackground(){
var gradient : CAGradientLayer = CAGradientLayer()
gradient.frame = self.tableView.bounds
gradient.colors = [UIColor.blueColor().CGColor, UIColor.redColor().CGColor]
self.tableView.layer.insertSublayer(gradient, atIndex: 0)
}
Messing around will setting the gradient doesn't work either, like setting:
self.view.layer.insertSublayer(gradient, atIndex: 0)
or changing the bounds:
gradient.frame = self.tableView.frame
Also, in cellForRowAtIndexPath I set the UITableViewCells background color to clear:
cell.backgroundColor = UIColor.clearColor()
I can't add images, but here is the link if you wish to see them: http://imgur.com/Vtka1tO,6faLMkr#0
The gradient needs to be behind the table view if you don't want it to scroll. If you're using a UITableViewController, the only thing behind is the window, so you could give it the gradient, and make the cells and the table view have a clear background color. If you're using a UIViewController with a table view as a subview, then you could give the controller's main view a gradient background color.
Have you thought to add a background image as follows?
var imageView = UIImageView(frame: CGRectMake(10, 10, cell.frame.width - 10, cell.frame.height - 10))
let image = UIImage(named: ImageNames[indexPath.row])
imageView.image = image
cell.backgroundView = UIView()
cell.backgroundView.addSubview(imageView)

Resources