I have a round Imageview in my ReusableCollectionView.
When I scroll down my collectionView I scale my Imageview and as soon as it scrolls back to place I scale it to its original size.
func scrollViewDidScroll(_ scrollView: UIScrollView) {
// Exit early if swiping up (scrolling down)
if scrollView.contentOffset.y > 0 { return }
// this is just a demo method on how to compute the scale factor based on the current contentOffset
var scale = 1.0 + fabs(scrollView.contentOffset.y) / scrollView.frame.size.height
//Cap the scaling between zero and 1
scale = max(0.0, scale)
// Set the scale to the imageView
headerView.imageview.transform = CGAffineTransform(scaleX: scale, y: scale)
headerView.categoryButton.transform = CGAffineTransform(scaleX: scale, y: scale)
}
The imageview is not round anymore while doing so.
Here is an image visualising the problem:
This is how I solved the problem:
imageView.autoresizesSubviews = false
Related
I am re-creating the iPhone App Switcher page where the app's tab view size is based off the location of it on the visual screen (bigger on the right and smaller on the left). I I have an array of views within a scroll view. I want to set the size of each view (tab) based on the location / content offset of the view as it scrolls horizontally across the visible screen x-axis.
Here's my code in scrollViewDidScroll:
func scrollViewDidScroll(_ scrollView: UIScrollView) {
tabViews.forEach { (tabView) in // tabViews: [UIView]
let screenWidth = UIScreen.main.bounds.width
// This should be a value between 0 and 1
var screenOffsetX = tabView.convert(CGPoint(x: tabView.frame.minX, y: 0),to: view).x
let maxValue: CGFloat = screenWidth / 8 // Max value is 1 + 1/8 scale size
if screenOffsetX > maxValue { // Set max scale
screenOffsetX = min(screenOffsetX, maxValue)
}
let minValue: CGFloat = 0
if screenOffsetX < minValue { // Set min scale
screenOffsetX = max(screenOffsetX, minValue)
}
let scaleAmount: CGFloat = 1 + (screenOffsetX / screenWidth)
let scaleTransform = CGAffineTransform(scaleX: scaleAmount, y: scaleAmount)
tabView.transform = scaleTransform
}
}
I think the math is off. I don't think the abcd.convert(point: ) is returning the correct content offset on the visual screen. Here's an image of the scroll view with views (tabs):
Each view should be slightly bigger on the right side of the screen and smaller on the left.
Any ideas?
tabView.convert won't return values between 0...1 for sure - it just translates a point to a different view coordinates.
Also it should be scrollView.convert, not tabView.convert.
Those checks can be simplified:
if screenOffsetX > maxValue { // Set max scale
screenOffsetX = min(screenOffsetX, maxValue)
}
There is no point of doing min after you already confirmed that maxValue is smaller. You either do:
if screenOffsetX > maxValue { // Set max scale
screenOffsetX = maxValue
}
or
screenOffsetX = min(screenOffsetX, maxValue)
I am not sure what is the usage in your code for maxValue and minValue, but if you want to scale things based on their position, I would suggest doing this:
func scrollViewDidScroll(_ scrollView: UIScrollView) {
tabViews.forEach { tabView in // tabViews: [UIView]
let screenWidth = UIScreen.main.bounds.width
var screenOffsetX = scrollView.convert(CGPoint(x: tabView.frame.minX, y: 0), to: view).x
var screenOffsetPercentage = screenOffsetX / screenWidth
let minValue: CGFloat = 0.5
let maxValue: CGFloat = 1
let scaleAmount = minValue + (maxValue - minValue) * screenOffsetPercentage
let scaleTransform = CGAffineTransform(scaleX: scaleAmount, y: scaleAmount)
tabView.transform = scaleTransform
}
}
You will have to adapt it to your idea, but as a start it scales from 1.0 scale at right side, to 0.5 scale at the left side.
Results looks like: https://media.giphy.com/media/m5KSgIInJmGjEHLZXb/giphy.gif
I have a question about how to set the size of UIImageView based on iPhone's display size dynamically.
If the size of a display of iPhone decreases, I want to make the size of UIImageView decreased proportionally.
I'm setting lowerThumbImageView and upperThumbImageView like below for now.
override init(frame: CGRect) {
super.init(frame: frame)
trackLayer.rangeSlider = self
// contentsScale: CGFloat - The scale factor applied to the layer.
// scale: CGFloat - The natural scale factor associated with the screen.
trackLayer.contentsScale = UIScreen.main.scale
// UIControl -> UIView -> layer
layer.addSublayer(trackLayer)
lowerThumbImageView.image = thumbImageA
addSubview(lowerThumbImageView)
upperThumbImageView.image = thumbImageB
addSubview(upperThumbImageView)
}
// 1
private func updateLayerFrames() {
// insetBy(dx:dy:) - Returns a rectangle that is smaller or larger
// than the source rectangle, with the same center point.
trackLayer.frame = bounds.insetBy(dx: 0.0, dy: bounds.height / 3)
trackLayer.setNeedsDisplay()
lowerThumbImageView.frame = CGRect(origin: thumbOriginForValue(lowerValue),
size: thumbImageA.size)
upperThumbImageView.frame = CGRect(origin: thumbOriginForValue(upperValue),
size: thumbImageB.size)
CATransaction.begin()
CATransaction.setDisableActions(true)
CATransaction.commit()
}
// 2
func positionForValue(_ value: CGFloat) -> CGFloat {
return bounds.width * value
}
// 3
private func thumbOriginForValue(_ value: CGFloat) -> CGPoint {
let x = positionForValue(value) - thumbImageA.size.width / 2.0
return CGPoint(x: x, y: (bounds.height * 0.001 - thumbImageA.size.height) / 2.0)
}
Could you give me your advice?
Thanks for reading.
You can set size of UIImageView proportional to any other View. Here are the steps to do it using Interface Builder Autolayout Constraints.
Go to Screen containing UIImageView on (Interface Builder/StoryBoard/XIB).
Press control button + drag mouse from UIImageView to SuperView or main View, Select "Equal Widths" & "Equal Heights".
Now from the right side in SizeInspector, Edit "Proportional Width" constraint & set its Multiplier value to 0.8 if you want to set width of UIImageView 80% to SuperView.
Edit "Proportional Height" constraint & set its Multiplier value to 0.7 if you want to set width of UIImageView 70% to SuperView.
I've attached images for your ease.
I have a label outside of a scrollview. I want to move the label from left to right of screen and vice versa when scrollview scrolling up and down.I wrote this code and it works when scrollview is scrolling in normal speed and when it scrolls very fast the label x position changes very slowly. How I can do that for all scrollview scrolling speeds?
func scrollViewDidScroll(_ scrollView: UIScrollView!) {
let offset = scrollView.contentOffset.y
let imahey = view.convert(placeImgview.frame, from:scrollview).origin.y + placeImgview.frame.width
print(offset)
let ratio: CGFloat = (-offset*1.0 / placeImgview.frame.height)
topBarView.alpha = -ratio
if placeName.frame.origin.x < -20 {
placeName.center = CGPoint(x: placeName.center.x - 3*ratio, y: placeName.center.y + ratio/1.5)
}
if (self.lastContentOffset > scrollView.contentOffset.y) {
placeName.center = CGPoint(x: placeName.center.x + 6*ratio, y: placeName.center.y - 2*ratio)
if offset == -20 {
placeName.frame.origin.x = -view.frame.width/2
placeName.frame.origin.y = 60
}
}
self.lastContentOffset = scrollView.contentOffset.y
}
So, the problem is that the faster you scroll, the larger the "gap" gets inbetween scrollViewDidScroll events.
You probably should consider to move around your label using UIView.animate... that would create a more consistent experience because the animation always has the same speed.
This way you could apply the animation using CGAffineTransform(translationX: , y: )
depending if your contentOffset.y passes a given threshold, whenever that label should appear or disappear.
I have an image which is set inside a scroll view, though I have set the frame of the scrollView to fixed height and width as shown below, the image goes beyond the bounds (see below picture).
How can I limit the picture to fit inside the scrollView.
imageScrollView.frame = CGRect(x: 0, y: 0, width: viewWidth, height: viewHeight-50)
imageScrollView.clipsToBounds = true // Has no affect on the image
Do you have a reference to the UIImageView? If so, then set its content mode to aspect fit. Like this:
theImageView.contentMode = .scaleAspectFit
The clipsToBounds you set only covers up any parts of child views that are sticking out of the bounds of the parent view, so that's why it doesn't do anything for you.
OR if you're using Interface Builder, set this option:
So, what if you don't have the reference to the UIImageView?...
You could iterate through the subviews of your scroll view, and whenever it finds a UIImageView, you can set the content mode like that. Something like:
//This is off the top of my head, so my filtering may not be right...
//This is also a one and done solution if you've got a lot of images in your scroll view
for anImgVw in imageScrollView.subviews.filter({$0.isKind(of: UIImageView.self)})
{
anImgVw.contentMode = .scaleAspectFit
}
Otherwise, I'm not sure if it's possible without a reference to the UIImageView.
The library you are using is coded to match the scaling to the device orientation. So, if the image orientation doesn't match the view orientation, you end up with the image not quite fitting in your scroll view.
You'll need to edit the ImageScrollView.swift source file. Assuming you're using the same version that is currently at the link you provided ( https://github.com/huynguyencong/ImageScrollView ), change the setMaxMinZoomScalesForCurrentBounds() function as follows:
fileprivate func setMaxMinZoomScalesForCurrentBounds() {
// calculate min/max zoomscale
let xScale = bounds.width / imageSize.width // the scale needed to perfectly fit the image width-wise
let yScale = bounds.height / imageSize.height // the scale needed to perfectly fit the image height-wise
// fill width if the image and phone are both portrait or both landscape; otherwise take smaller scale
//let imagePortrait = imageSize.height > imageSize.width
//let phonePortrait = bounds.height >= bounds.width
//var minScale = (imagePortrait == phonePortrait) ? xScale : min(xScale, yScale)
//
// just take the min scale, so the image will completely fit regardless of orientation
var minScale = min(xScale, yScale)
let maxScale = maxScaleFromMinScale*minScale
// don't let minScale exceed maxScale. (If the image is smaller than the screen, we don't want to force it to be zoomed.)
if minScale > maxScale {
minScale = maxScale
}
maximumZoomScale = maxScale
minimumZoomScale = minScale * 0.999 // the multiply factor to prevent user cannot scroll page while they use this control in UIPageViewController
}
you can use the screenHeight rather than the viewHeight
let screenSize: CGRect = UIScreen.mainScreen().bounds
let screenWidth = screenSize.width
let screenHeight = screenSize.height
imageScrollView.frame = CGRect(x: 0, y: 0, width: viewWidth, height: screenHeight-50)
I assigned the pinch gesture to uiview:
myView.addGestureRecognizer(UIPinchGestureRecognizer(target: self, action: "handlePinch:"))
This function will scale the uiview:
func handlePinch(recognizer : UIPinchGestureRecognizer) {
if let view = recognizer.view {
view.transform = CGAffineTransformScale(view.transform,
recognizer.scale, recognizer.scale)
recognizer.scale = 1
}
}
but the scale is not limited within the parent view. and when i try to scale it down it has also no limit, it scales till it disappears.
The grey view is the parent
My Question is
How can it be prevented from exceeding the parent frame on scale up and disappearing on scale down?
You could use something like this:
if let view = recognizer.view, parent = recognizer.view.superview {
// this will only let it scale to half size
let minimumThreshold: CGFloat = 0.5
var scale: CGFloat = recognizer.scale
// assuming your view is square, which based on your example it is
let newSize = view.frame.height * scale
// prevents the view from growing larger than the smallest dimension of the parent view
let allowableSize = min(parent.frame.height, parent.frame.width)
let maximumScale: CGFloat = allowableSize/view.frame.height
// change scale if it breaks either bound
if scale < minimumThreshold {
print("size is too small")
scale = minimumThreshold
}
if newSize > allowableSize {
print("size is too large")
scale = maximumScale
}
// apply the transform
view.transform = CGAffineTransformMakeScale(scale, scale)
}
The key here is to decide on a lower bound based on preference, and an upper bound based on the ratio of your view's initial size to the parent's size. By doing so, the view cannot be shrunk smaller than desired, as assumedly the user should not be able to scale down to a point where they cannot resize due to difficulty pinching. In addition, the view should not be allowed to be scaled infinitely, thus overflowing its bounds. This check does just that, as it allows only a range of sizes you dictate to be set by the user's interactions.
I wrote a function to test if the pinch is out of parent view's boundary.
/// test if an origin frame scale would be within the boundary of parent view
func scaleWithinBoundary(originFrame:CGRect, parent:UIView, scale:CGFloat) -> Bool {
let height = originFrame.height * scale
let width = originFrame.width * scale
let leftX = originFrame.midX - width/2, rightX = originFrame.midX + width/2
let topY = originFrame.midY - height/2, bottomY = originFrame.midY + height/2
return leftX >= 0 && topY >= 0 && leftX >= parent.frame.minX && rightX <= parent.frame.maxX
&& topY >= parent.frame.minY && bottomY <= parent.frame.maxY
}