UICollectionViewCell with custom UICollectionViewLayout to create Parallax Animation On Just one subview - ios

Inspired by this tutorial [Custom UICollectionViewLayout Tutorial With Parallax][1]
[1]: https://www.raywenderlich.com/527-custom-uicollectionviewlayout-tutorial-with-parallax I created my own version of parallax collection view.
I am creating the parallax animation and bounce affect on the header, and footer with this logic:
private func updateSupplementaryViews(_ type: Element, attributes: CustomLayoutAttributes, collectionView: UICollectionView, indexPath: IndexPath) {
if type == .header,
settings.isHeaderStretchy {
var delta: CGFloat = 0.0
let updatedHeight = min(
collectionView.frame.height + headerSize.height,
max(headerSize.height , headerSize.height - contentOffset.y))
let scaleFactor = updatedHeight / headerSize.height
if contentOffset.y <= 0{
delta = (updatedHeight - headerSize.height) / 2
}else{
delta = (headerSize.height - updatedHeight - abs(contentOffset.y))
}
let scale = CGAffineTransform(scaleX: scaleFactor, y: scaleFactor)
let translation = CGAffineTransform(translationX: 0, y: min(contentOffset.y, headerSize.height) + delta)
attributes.transform = scale.concatenating(translation)
}else if type == .footer,
settings.isFooterStretchy {
var updatedHeight: CGFloat = 0.0
if collectionView.contentSize.height - contentOffset.y < UIScreen.main.bounds.height {
updatedHeight = min(
collectionView.frame.height + footerSize.height,
max(footerSize.height, abs(((collectionView.contentSize.height - (footerSize.width + deviationDeviceSize)) - UIScreen.main.bounds.height) - contentOffset.y)))
}else{
updatedHeight = min(
collectionView.frame.height + footerSize.height,
max(headerSize.height, headerSize.height - contentOffset.y))
}
let scaleFactor = updatedHeight / footerSize.height
let delta = ((updatedHeight - footerSize.height) / 2) - footerSize.height
let scale = CGAffineTransform(scaleX: scaleFactor, y: scaleFactor)
let translation = CGAffineTransform(translationX: 0, y: min(contentOffset.y, footerSize.height) + delta )
attributes.transform = scale.concatenating(translation)
}
}
Everything works great beside the annoying fact that all my cell subviews are scaling with this animation and I would like to scale only my background image. This seems simple enough but unfortunately I couldn't figure out a way to control what subviews are being manipulated by the transformation.

What have you set as customAttributes for Element.footer ?
What is the footerSize you are setting ?
You have included some implementations around footer in your project which is not visible for us? Mind to share the whole file ?

Related

UITableViewCell desn't update it's heigh to fit custom UIControl height

I've created a custom slider inherited from UIControl and pass it into UITableViewCell.
The problem is that when the content of my custom slider is bigger than UITableViewCell's height, the cell won't update it's height to fit slider's content height
Here is my hierarchy of TableViewCell
This is how it looks like(the background blue view is a cell). All views I created in code in custom slider control:
This is how it looks like on the device:
As you can see I have a larger content of my custom control, but the cell doesn't want to update its constraints to fit control's content. One more thing, as you can see on the first screenshot, I have height constraint >= 60, but if I remove this constraint, than my custom slider won't be visible at all:
Here is my code of custom slider, but I guess I messed something of updating constraints of parent view(UITableViewCell) or calculating setting parentView's(UITableViewCell) content height:
private func updateValueLabelFrame() {
func positionForValueInTrack() -> CGFloat {
return trackView.frame.width * ((value - minimumValue) / (maximumValue - minimumValue))
}
func originForValueInTrack(height: CGFloat) -> CGPoint {
let x = positionForValueInTrack() - height / 2
return CGPoint(x: x, y: (trackView.frame.height - height) / 2)
}
trackView.setNeedsDisplay()
valueLabel.sizeToFit()
let biggerSize = valueLabel.frame.width > valueLabel.frame.height ? valueLabel.frame.width : valueLabel.frame.height
var valueViewHeight = self.frame.height - (self.frame.height * 0.3)
if biggerSize > valueViewHeight {
valueViewHeight = biggerSize + 20
self.frame.size = CGSize(width: self.frame.width, height: valueViewHeight / 0.7)
self.setNeedsLayout()
}
let valueViewSize = CGSize(width: valueViewHeight, height: valueViewHeight)
self.valueView.frame = CGRect(origin: originForValueInTrack(height: valueViewHeight), size: valueViewSize)
let valueLabelSize = valueLabel.sizeThatFits(valueViewSize)
let valueLabelOrigin = CGPoint(x: valueView.bounds.midX - valueLabelSize.width / 2, y: valueView.bounds.midY - valueLabelSize.height / 2)
valueLabel.frame = CGRect(origin: valueLabelOrigin, size: valueLabelSize)
valueView.layer.cornerRadius = valueView.frame.size.width / 2
}
private func updateFrame() {
minimumLabel.sizeToFit()
maximumLabel.sizeToFit()
minimumLabel.frame.origin = CGPoint(x: self.bounds.minX + minMaxLabelsOffsetToBorder, y: self.bounds.midY - minimumLabel.frame.height / 2)
maximumLabel.frame.origin = CGPoint(x: self.bounds.maxX - (maximumLabel.frame.width + minMaxLabelsOffsetToBorder), y: self.bounds.midY - maximumLabel.frame.height / 2)
let trackLayerSize = CGSize(width: (maximumLabel.frame.minX - trackLayerOffsetFromLabels) - (minimumLabel.frame.maxX + trackLayerOffsetFromLabels), height: self.bounds.height / 3)
let trackLayerOrigin = CGPoint(x: minimumLabel.frame.maxX + trackLayerOffsetFromLabels, y: self.bounds.midY - trackLayerSize.height / 2)
trackView.frame = CGRect(origin: trackLayerOrigin, size: trackLayerSize)
createReplicatorLayer()
updateValueLabelFrame()
}
As per OP...
You're missing constraints that are needed to auto-size the height of the cell.

issue while dropping pin to imageview on different devices

i am dropping pin image on imageview with coordinates so pin set correctly but there is minor position difference in various devices i am sharing my code below
func convertTapToImg(_ point: CGPoint) -> CGPoint? {
let xRatio = imgView.frame.width / (img?.size.width)!
let yRatio = imgView.frame.height / (img?.size.height)!
let ratio = min(xRatio, yRatio)
let imgWidth = (img?.size.width)! * ratio
let imgHeight = (img?.size.height)! * ratio
var tap = point
var borderWidth: CGFloat = 0
var borderHeight: CGFloat = 0
// detect border
if ratio == yRatio {
// border is left and right
borderWidth = (imgView.frame.size.width - imgWidth) / 2
if point.x < borderWidth || point.x > borderWidth + imgWidth {
return nil
}
tap.x -= borderWidth
} else {
// border is top and bottom
borderHeight = (imgView.frame.size.height - imgHeight) / 2
if point.y < borderHeight || point.y > borderHeight + imgHeight {
return nil
}
tap.y -= borderHeight
}
let xScale = tap.x / (imgView.frame.width - 2 * borderWidth)
let yScale = tap.y / (imgView.frame.height - 2 * borderHeight)
let pixelX = (img?.size.width)! * xScale
let pixelY = (img?.size.height)! * yScale
return CGPoint(x: pixelX, y: pixelY)
}
then with tap-gesture i have fetched one x y coordinates as below
#objc func tapGesture(_ gesture: UITapGestureRecognizer) {
let point = gesture.location(in: imgView)
let imgPoint = convertTapToImg(point)
print("tap: \(point) -> img \(imgPoint)")
}
then after i am set up pin like below with coordinates
var xCo = 83.0
var yCo = 404.0
let imageView = UIImageView()
imageView.image = #imageLiteral(resourceName: "ic_Pin")
imageView.frame = CGRect(x: xCo - 20, y: yCo - 20, width: 22, height: 22)
imgView.addSubview(imageView)
now i am sharing screen shot of various devices output there is just minor difference with pin position
This is iPhone 5s Screen Shot
This is iPhone 6 Screen Shot
This is iPhone XR Screen Shot
please check screen shot and please help that how to set pin position same on all devices

Spritekit Camera Node scale but pin the bottom of the scene

I have a camera node that is scaled at 1. When I run the game, I want it to scale it down (i.e. zoom out) but keep the "floor" at the bottom. How would I go about pinning the camera node to the bottom of the scene and effectively zooming "up" (difficult to explain). So the bottom of the scene stays at the bottom but the rest zooms out.
I have had a go with SKConstraints but not having any luck (I'm quite new at SpriteKit)
func setConstraints(with scene: SKScene, and frame: CGRect, to node: SKNode?) {
let scaledSize = CGSize(width: scene.size.width * xScale, height: scene.size.height * yScale)
let boardContentRect = frame
let xInset = min((scaledSize.width / 2), boardContentRect.width / 2)
let yInset = min((scaledSize.height / 2), boardContentRect.height / 2)
let insetContentRect = boardContentRect.insetBy(dx: xInset, dy: yInset)
let xRange = SKRange(lowerLimit: insetContentRect.minX, upperLimit: insetContentRect.maxX)
let yRange = SKRange(lowerLimit: insetContentRect.minY, upperLimit: insetContentRect.maxY)
let levelEdgeConstraint = SKConstraint.positionX(xRange, y: yRange)
if let node = node {
let zeroRange = SKRange(constantValue: 0.0)
let positionConstraint = SKConstraint.distance(zeroRange, to: node)
constraints = [positionConstraint, levelEdgeConstraint]
} else {
constraints = [levelEdgeConstraint]
}
}
then calling the function with:
gameCamera.setConstraints(with: self, and: scene!.frame, to: nil)
(This was code from a tutorial I was following) The "setConstraints" function is an extension of SKCameraNode
I'm not sure this will give me the correct output, but when I run the code to scale, it just zooms from the middle and shows the surrounding area of the scene .sks file.
gameCamera.run(SKAction.scale(to: 0.2, duration: 100))
This is the code to scale the gameCamera
EDIT: Answer below is nearly what I was looking for, this is my updated answer:
let scaleTo = 0.2
let duration = 100
let scaleTop = SKAction.customAction(withDuration:duration){
(node, elapsedTime) in
let newScale = 1 - ((elapsedTime/duration) * (1-scaleTo))
let currentScaleY = node.yScale
let currentHeight = node.scene!.size.height * currentScaleY
let newHeight = node.scene!.size.height * newScale
let heightDiff = newHeight - currentHeight
let yOffset = heightDiff / 2
node.setScale(newScale)
node.position.y += yOffset
}
You cannot use a constraint because your scale size is dynamic.
Instead you need to move your camera position to give the illusion it is only scaling in 3 directions.
To do this, I would recommend creating a custom action.
let scaleTo = 2.0
let duration = 1.0
let currentNodeScale = 0.0
let scaleTop = SKCustomAction(withDuration:duration){
(node, elapsedTime) in
if elapsedTime == 0 {currentNodeScale = node.scale}
let newScale = currentNodeScale - ((elapsedTime/duration) * (currentNodeScale-scaleTo))
let currentYScale = node.yScale
let currentHeight = node.scene.size.height * currentYScale
let newHeight = node.scene.size.height * newScale
let heightDiff = newHeight - currentHeight
let yOffset = heightDiff / 2
node.scale(to:newScale)
node.position.y += yOffset
}
What this is doing is comparing the new height of your camera with the old height, and moving it 1/2 the distance.
So if your current height is 1, this means your camera sees [-1/2 to 1/2] on the y axis. If you new scale height is 2, then your camera sees [-1 to 1] on the y axis. We need to move the camera up so that the camera sees [-1/2 to 3/2], meaning we need to add 1/2. So we do 2 - 1, which is 1, then go 1/2 that distance. This makes our yOffset 1/2, which you add to the camera.

Custom Layout on UICollectionView using Swift 3

Desire Layout on CollectionView
I having trouble achieving a custom layout on collectionview in swift 3.0. Its been two days searching on similar layout but I couldn't find one. Pls anyone help me to achieve my desire custom layout.
There are many ways of doing and there are many 3rd party lib to help out. But I suspect you don't know how to do layout and spacing among cell.
Here is the best help ..just play with values to get your similar layout.
override func prepareLayout() {
// 1
if cache.isEmpty {
// 2
let columnWidth = contentWidth / CGFloat(numberOfColumns)
var xOffset = [CGFloat]()
for column in 0 ..< numberOfColumns {
xOffset.append(CGFloat(column) * columnWidth )
}
var column = 0
var yOffset = [CGFloat](count: numberOfColumns, repeatedValue: 0)
// 3
for item in 0 ..< collectionView!.numberOfItemsInSection(0) {
let indexPath = NSIndexPath(forItem: item, inSection: 0)
// 4
let width = columnWidth - cellPadding * 2
let photoHeight = delegate.collectionView(collectionView!, heightForPhotoAtIndexPath: indexPath,
withWidth:width)
let annotationHeight = delegate.collectionView(collectionView!,
heightForAnnotationAtIndexPath: indexPath, withWidth: width)
let height = cellPadding + photoHeight + annotationHeight + cellPadding
let frame = CGRect(x: xOffset[column], y: yOffset[column], width: columnWidth, height: height)
let insetFrame = CGRectInset(frame, cellPadding, cellPadding)
// 5
let attributes = UICollectionViewLayoutAttributes(forCellWithIndexPath: indexPath)
attributes.frame = insetFrame
cache.append(attributes)
// 6
contentHeight = max(contentHeight, CGRectGetMaxY(frame))
yOffset[column] = yOffset[column] + height
column = column >= (numberOfColumns - 1) ? 0 : ++column
}
}
}
Reference UIcollectionView Layout

Trying to animate a SKNode inside a UIView

I have programmatically created a SKView inside of my UIView. Now I want to animate a new SKNodes in all directions, starting in the middle (this works) depending on the view that is passed through. All works fine except for the fact that the ending position of the SKNode is weird. Instead of shooting in all directions, it is going inside of a corner, way out of the view's boundaries. It should never go out of the view's boundaries. I am converting the CGPoint to my scene from the View.
This is my code:
func animateExplosion(sender: UIButton){
let star = SKSpriteNode(imageNamed: "ExplodingStar")
let starHeight = sender.frame.height / 3
star.size = CGSize(width: starHeight, height: starHeight)
var position = sender.frame.origin
position = self.sceneScene.convertPoint(fromView: position)
star.position = position
let minimumDuration = 1
let maximumDuration = 2
let randomDuration = TimeInterval(RandomInt(min: minimumDuration * 100, max: maximumDuration * 100) / 100)
let fireAtWill = SKAction.move(to: getRandomPosition(view: sender), duration: randomDuration)
let rotation = SKAction.rotate(byAngle: CGFloat(randomAngle()), duration: Double(randomDuration))
let fadeOut = SKAction.fadeOut(withDuration: randomDuration)
let scaleTo = SKAction.scale(to: starHeight * 2, duration: randomDuration)
let group = SKAction.group([fireAtWill, rotation]) //for testing no fade out or remove parent
let sequence = SKAction.sequence([group])
if randomAmountOfExplodingStars > 0{
randomAmountOfExplodingStars -= 1
sceneScene.addChild(star)
star.run(sequence)
animateExplosion(sender: sender)
}
}
the getRandomPosition where the bug properly is:
func getRandomPosition(view: UIView) -> CGPoint{
let direction = RandomInt(min: 1, max: 4)
var randomX = Int()
var randomY = Int()
if direction == 1{
randomX = Int(view.frame.width / 2)
randomY = RandomInt(min: -Int(view.frame.height / 2), max: Int(view.frame.height / 2))
}
if direction == 2{
randomX = RandomInt(min: -Int(view.frame.width / 2), max: Int(view.frame.width / 2))
randomY = Int(view.frame.height / 2)
}
if direction == 3{
randomX = -Int(view.frame.width / 2)
randomY = RandomInt(min: -Int(view.frame.height / 2), max: Int(view.frame.height / 2))
}
if direction == 4{
randomX = RandomInt(min: -Int(view.frame.width / 2), max: Int(view.frame.width / 2))
randomY = -Int(view.frame.height / 2)
}
var randomPosition = CGPoint(x: randomX, y: randomY)
//randomPosition = self.sceneScene.convertPoint(fromView: randomPosition)
return randomPosition
}
I know that code looks awful, but it should do the trick right? The passed through view is a UIButton inside of a UIView. The SKView shares exactly the same constrains as that UIView. The animation should start in the middle and end somewhere to the boundaries of the passed view.
Yay got it to work finally. So dumb I did not notice before. The sender would be always a child inside of a UIView which is smaller. The return function was great, but the SKNode should not move to the returned function, but moved by.
Updated code:
let randomPositionOfSender = getRandomPosition(view: sender)
let fireAtWill = SKAction.moveBy(x: randomPositionOfSender.x, y: randomPositionOfSender.y, duration: randomDuration)

Resources