Make Top image in UIScrollView enlarge on scroll view bounds? - ios

I have a UIScroll view with a UIView inside as content view. The layout is:
self.scrollView = UIScrollView()
self.view.addSubview(self.scrollView)
self.scrollView.backgroundColor = UIColor.yellowColor()
// pin all edges to the edges of the superview (self.view)
self.scrollView.snp_makeConstraints { (make) -> Void in
make.edges.equalTo(self.view)
}
// create contentView
self.contentView = UIView()
self.contentView.backgroundColor = UIColor.redColor()
self.scrollView.addSubview(self.contentView)
// pin the edges of the contentView to the scrollView
self.contentView.snp_makeConstraints { (make) -> Void in
make.edges.equalTo(self.scrollView)
}
In my contentView I have a UIImageView at the top and some UILabels below:
let imageView = UIImageView(...)
let labe1 = UILabel(...)
let labe2 = UILabel(...)
self.contentView.addSubView(imageView)
self.contentView.addSubView(labe1)
self.contentView.addSubView(labe2)
Here is how it looks like:
When I pull down the scroll view the scroll view bounces at the top. I want that the image enlarges when the scroll view bounces.
I did this with the UIScrollViewDelegate:
func scrollViewDidScroll(scrollView: UIScrollView) {
let offsetY: CGFloat = scrollView.contentOffset.y
if offsetY < -64 {
let progress:CGFloat = fabs(offsetY + 64) / 100
self.imageView.transform = CGAffineTransformMakeScale(1 + progress, 1 + progress)
}
}
This works but the problem is that when the image is transformed it should not overlay the border to the red area which contains the Labels. Here is what happens when I bounce:
I want that the image still enlarges but not overlapping the border to the red area. How can I do that?

I suggest you to resize the whole UIScrollView with pager view - don't forget to reset its's frame back to prevent wrong paging.

Just add an empty UIView with some background color as highlighted in image
Or in your code
let imageView = UIImageView(...)
let labe1 = UILabel(...)
let labe2 = UILabel(...)
self.contentView.addSubView(imageView)
//add one more UIView with some background color here
self.contentView.addSubView(labe1)
self.contentView.addSubView(labe2)

Related

SWIFT - Setting layer.cornerRadius greater than the view height causes issues

I actually want to add a corner radius to a view on only one side. The radius is supposed to be of full height. This is my implementation.
someView.layer.cornerRadius = someView.frame.size.height
someView.layer.maskedCorners = [.layerMaxXMaxYCorner]
This does the job as required, but it adds extra shapes on other sides as shown below.
What might be the problem?
This looks like an iOS bug, I was able to reproduce it
But usually you don't wanna set cornerRadius greater than half view minimum side, in your case:
someView.layer.cornerRadius = someView.frame.size.height / 2
I assume it'll produce result you're expecting:
Corner radius is radius of a circle inscribed in the corner of a rectangle, I think that's why there may be problems with radius bigger than side/2: circle doesn't fit a rectangle anymore
i had the same problem. I needed to make the height of the view 16 and the bottom corners radius also 16.
My solution:
add the view you want to round to the bottom view
clip the bottomView to specific size (in my case width = window width and height=16)
make height of the rounded view = cornerRadius * 2 (in my case 32)
pin rounded view to superView, excluding the opposite edge which you want to round off (in my case i wanted to round bottom edge roundedView.autoPinEdgesToSuperviewEdges(with: .zero, excludingEdge: .top))
code
private let bottomView: UIView = {
let view = UIView(forAutoLayout: ())
view.clipsToBounds = true
return view
}()
private let roundedView: UIView = {
let view = UIView(forAutoLayout: ())
view.clipsToBounds = true
view.contentMode = .scaleAspectFill
view.layer.maskedCorners = [.layerMinXMaxYCorner, .layerMaxXMaxYCorner]
return view
}()
...
contentView.addSubview(bottomView)
bottomView.addSubview(roundedView)
...
func setupConstraints() {
if roundedView.layer.cornerRadius >= roundedView.frame.height / 2 {
roundedView.autoSetDimension(.height, toSize: roundedView.layer.cornerRadius * 2)
} else {
roundedView.autoPinEdge(toSuperviewEdge: .top)
}
roundedView.autoPinEdgesToSuperviewEdges(with: .zero, excludingEdge: .top)
bottomView.autoPinEdgesToSuperviewEdges()
}
result:
enter image description here

How can I adjust the the size of the circle to dynamically adjust to the width and height of any iPhone?

I am using snap kit to set out constraints. The first image is what I'm trying to achieve with the code below. How can I set the constraints off the circle's width and height to be dynamic on any iPhone screen ?
profileImage = UIImageView()
profileImage.layer.borderWidth = 2
profileImage.layer.borderColor = UIColor.lightBlue.cgColor
profileImage.layer.cornerRadius = 130
profileImage.clipsToBounds = true
profileImage.layer.masksToBounds = true
let tapGesture = UITapGestureRecognizer(target: self, action:#selector((tappedImage)))
profileImage.addGestureRecognizer(tapGesture)
profileImage.isUserInteractionEnabled = true
profileImage.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(profileImage)
profileImage.snp.makeConstraints { (make) in
make.centerX.equalToSuperview()
make.top.equalTo(titleLabel.snp.bottom).offset(topMargin*2)
make.width.height.equalTo(view.snp.width).multipliedBy(0.71)
}
enter image description here
enter image description here
Couple points...
First, 71% of the view width will probably be too big. Start around 50% and adjust to your liking.
You are using .cornerRadius = 130 but your imageView may not be that size (certainly not on different devices), so you want to set the corner radius to one-half the width of the image view (or height, doesn't matter since it will be a square 1:1 ratio).
You could wait until viewDidLayoutSubviews() to find out the run-time size of the image view, but if your image view ends up as a subview of another view, it won't be set at that point either.
Much easier to create a very simple UIImageView subclass:
class ProfileImageView: UIImageView {
override func layoutSubviews() {
super.layoutSubviews()
layer.borderWidth = 2
layer.borderColor = UIColor.systemBlue.cgColor
layer.cornerRadius = bounds.width * 0.5
clipsToBounds = true
layer.masksToBounds = true
}
}
Then your view controller looks like this:
class ViewController: UIViewController {
var profileImage: ProfileImageView!
// your top margin value
let topMargin: CGFloat = 20
override func viewDidLoad() {
super.viewDidLoad()
profileImage = ProfileImageView()
if let img = UIImage(named: "sampleProfilePic") {
profileImage.image = img
}
profileImage.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(profileImage)
// respect safe-area
let g = view.safeAreaLayoutGuide
// this should give the same layout as your "snap" constraints
NSLayoutConstraint.activate([
// center horizontally
profileImage.centerXAnchor.constraint(equalTo: g.centerXAnchor),
// your topMargin value * 2 from safe-area top
profileImage.topAnchor.constraint(equalTo: g.topAnchor, constant: topMargin * 2.0),
// width
// 71% of width of safe-area is probably too wide
// try starting at 50%
profileImage.widthAnchor.constraint(equalTo: g.widthAnchor, multiplier: 0.50),
// 1:1 ratio (square)
profileImage.heightAnchor.constraint(equalTo: profileImage.widthAnchor),
])
// profileImage.snp.makeConstraints { (make) in
// make.centerX.equalToSuperview()
// make.top.equalTo(titleLabel.snp.bottom).offset(topMargin*2)
// make.width.height.equalTo(view.snp.width).multipliedBy(0.71)
// }
let tapGesture = UITapGestureRecognizer(target: self, action:#selector((tappedImage)))
profileImage.addGestureRecognizer(tapGesture)
profileImage.isUserInteractionEnabled = true
}
}
Whenever the size of your profileImage view changes, the ProfileImageView class' layoutSubviews() will automatically set the corner radius appropriately.

UIImageView in UIScrollView does not zoom correctly

I want to allow the user to side pan an image. The image should be scaled to the height of the device and the user is supposed to only be able to scroll left and right. The users is not supposed to be able to zoom.
I have a UIViewController, to which I add a custom subclass ImageScrollView.
This is supposed to display an image in full height, but instead the image is basically displayed un-zoomed. Even though the zoomScale gets calculated correctly, but does not have an effect.
What am I doing wrong?
Thanks
override func viewDidLoad() {
super.viewDidLoad()
let i = UIImage(named: "test.jpg")
let iSV = ImageScrollView(image: i)
self.view.addSubview(iSV)
iSV.fillSuperview()
}
class ImageScrollView: UIScrollView {
let image: UIImage
let imageView = UIImageView()
init(image img: UIImage) {
image = img
imageView.image = image
super.init(frame: .zero)
self.addSubview(imageView)
imageView.fillSuperview()
self.contentSize = image.size
self.showsHorizontalScrollIndicator = false
self.showsVerticalScrollIndicator = false
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func layoutSubviews() {
super.layoutSubviews()
self.zoomScale = getZoomScale()
}
func getZoomScale() -> CGFloat{
let boundSize = self.frame.size
let yScale = boundSize.height / image.size.height
return yScale
}
}
And just for the case it has to do with auto-layout I included the fillSuperview extension.
extension UIView {
public func fillSuperview() {
translatesAutoresizingMaskIntoConstraints = false
if let superview = superview {
topAnchor.constraint(equalTo: superview.topAnchor, constant: 0).isActive = true
leftAnchor.constraint(equalTo: superview.leftAnchor, constant: 0).isActive = true
bottomAnchor.constraint(equalTo: superview.bottomAnchor, constant: 0).isActive = true
rightAnchor.constraint(equalTo: superview.rightAnchor, constant: 0).isActive = true
}
}
}
Since you don't want the image to zoom, I recommend you don't even bother with the zoom controls. A UIImageView knows how to scale its content.
I recommend you do it like this:
Add a constraint that sets the imageView height equal to the scrollView height. This will prevent vertical scrolling.
Add a constraint that sets the imageView width equal to the imageView height with multiplier image.size.width / image.size.height.
Set imageView content mode to .scaleToFill.
To allow you to change the image, keep an aspectRatio property that retains the aspect ratio constraint set in step 2. Set aspectRatio.isActive = false, and then create and activate a new constraint for the new image.
Also, if you might ever have images that aren't wide enough to fill the scrollView horizontally when scaled to fit vertically, consider these changes:
Replace the constraint that sets the imageView width with one that sets the width equal to the imageView height with multiplier max(image.size.width / image.size.height, scrollView.bounds.width / scrollView.bounds.height).
Set imageView content mode to .scaleAspectFit.
Then, when you have a narrow image, the imageView will fill the scrollView, but the .scaleAspectFit will show the entire image centered in the scrollView. This will still work correctly for wide images because the multiplier will match the image aspect ratio and .scaleAspectFit will fill the entire imageView.
You forgot to implement scrollview delegate. And set min & max zoom level for scrollview.
var iSV: ImageScrollView?
let i = UIImage(named: "noWiFi")!
iSV = ImageScrollView(image: i)
if let iSV = iSV {
self.view.addSubview(iSV)
iSV.fillSuperview()
iSV.delegate = self
}
override func viewDidLayoutSubviews() {
if let iSV = iSV {
let scale = iSV.getZoomScale()
iSV.minimumZoomScale = scale
iSV.maximumZoomScale = scale
}
}
extension ViewController: UIScrollViewDelegate {
func viewForZooming(in scrollView: UIScrollView) -> UIView? {
return iSV?.imageView
}
}
Note: I just did rough. It may not fulfil your complete requirement

How do I create, add and position a UIScrollView programmatically so that it fills the screen?

I want to create a simple view hierarchy in a Swift project that has a UIScrollView which should act as an expandable container to the subviews which are: UILabel, UITextView and a Button.
The catch here is that I am doing it all programmatically using visual format language and cannot do it in Interface Builder. My code below displays a scrollview, however it cannot scroll down to show the views which are below it and the views themselves are not sized correctly. It is static. Also the subviews are not expanding to fill up the screen.
I want to display a scrollview which is fullscreen size and also its subviews fill the screen horizontally with varying heights depending on what size I set. They currently only are around 200pt wide which is unusual.
viewDidLoad() {
view.addSubview(scrollView)
//This function is a convenience function which applies constraints
view.addConstraints(withFormat: "H:|[v0]|", toViews: scrollView)
view.addConstraints(withFormat: "V:|[v0]|", toViews: scrollView)
//Here I add the 3 subviews mentioned above
scrollView.addSubview(nativeText)
scrollView.addSubview(mnemonicDescription)
scrollView.addSubview(addButton)
//Here I apply constraints using format language
scrollView.addConstraints(withFormat: "H:|[v0]|", toViews: foreignText)
// Note that this should make foreignText expand the full width. It doesn't, its very small
//I continue to add subviews with horizontal and vertical constraints however they do not fill the container view as expected.
}
Here is a (fairly) simple example of adding 3 views to a Scroll View and using VFL to set the constraints.
override func viewDidLoad() {
super.viewDidLoad()
// create a scroll view with gray background (so we can see it)
let theScrollView = UIScrollView()
theScrollView.backgroundColor = .gray
// add it to the view
view.addSubview(theScrollView)
// add constraints so the scroll view fills the view
view.addConstraintsWithFormat("H:|[v0]|", views: theScrollView)
view.addConstraintsWithFormat("V:|[v0]|", views: theScrollView)
// create three UIViews - nativeText (red), mnemonicDescription (green), addButton (blue)
let nativeText = UIView()
nativeText.translatesAutoresizingMaskIntoConstraints = false
nativeText.backgroundColor = .red
let mnemonicDescription = UIView()
mnemonicDescription.translatesAutoresizingMaskIntoConstraints = false
mnemonicDescription.backgroundColor = .green
let addButton = UIView()
addButton.translatesAutoresizingMaskIntoConstraints = false
addButton.backgroundColor = .blue
// add those three views to the scroll view
theScrollView.addSubview(nativeText)
theScrollView.addSubview(mnemonicDescription)
theScrollView.addSubview(addButton)
// set horizontal / width constraints for the three views so they fill the scroll view
// "H:|[v0(==v1)]|" means "make the width of v0 equal to the width of v1, and pin to leading and trailing"
theScrollView.addConstraintsWithFormat("H:|[v0(==v1)]|", views: nativeText, theScrollView)
theScrollView.addConstraintsWithFormat("H:|[v0(==v1)]|", views: mnemonicDescription, theScrollView)
theScrollView.addConstraintsWithFormat("H:|[v0(==v1)]|", views: addButton, theScrollView)
// set the vertical / height constraints of the three views
// (==200) means "set the height to 200"
// "|" means "pin to edge"
// "-40-" means 40 points of space
// so the following 3 lines will put:
// nativeText (Red view) pinned to the top of scrollview, height of 200
theScrollView.addConstraintsWithFormat("V:|[v0(==200)]", views: nativeText)
// mnemonicDescription (Green view) pinned 40 space to Red view, height of 300
theScrollView.addConstraintsWithFormat("V:[v0]-40-[v1(==300)]", views: nativeText, mnemonicDescription)
// addButton (Blue view) pinned 40 space to Green view, height of 250, *and* pinned to bottom of scrollview
theScrollView.addConstraintsWithFormat("V:[v0]-40-[v1(==250)]|", views: mnemonicDescription, addButton)
// it could also be expressed in a single statement
// comment out the above three lines of code, and
// un-comment this line to see the same result
//theScrollView.addConstraintsWithFormat("V:|[v0(==200)]-40-[v1(==300)]-40-[v2(==250)]|", views: nativeText, mnemonicDescription, addButton)
// using those example heights and spacing comes to a total of 830,
// so it will scroll vertically a little bit on a iPhone 7+ (736 pts tall)
}
This is not a very difficult thing to achieve. If you will research carefully you can find the answers to all you queries here itself.
Anyways I am providing you with the detailed code for the thing you want to achieve. Hope this help you.
Simply add this code below your ViewController Class.
let scrollView = UIScrollView()
override func viewDidLoad() {
super.viewDidLoad()
// Variables for setting the scrollView and Views inside scrollView
let phoneHeight: CGFloat = self.view.frame.size.height
let phoneWidth: CGFloat = self.view.frame.size.width
var yPosition: CGFloat = 0
var scrollViewHeight: CGFloat = 0
// Setting scrollView attributes
scrollView.frame.size.height = phoneHeight
scrollView.frame.size.width = phoneWidth
scrollView.frame.origin.x = 0
scrollView.frame.origin.y = 0
// Adding scrollView to the mainView
self.view.addSubview(scrollView)
// Creating subViews for the scrollView
let view1 = UIView()
let view2 = UIView()
let view3 = UIView()
let view4 = UIView()
let view5 = UIView()
// Adding the subVies in an array
var subViewArray: [UIView] = []
subViewArray.append(view1)
subViewArray.append(view2)
subViewArray.append(view3)
subViewArray.append(view4)
subViewArray.append(view5)
// Adding the subViews to the scrollView
for subView in subViewArray {
// Setting the atributes for subViews
// Fixed height of 200px and width adjusts according to the phoneSize
subView.frame.size.height = 200
subView.frame.size.width = phoneWidth
subView.frame.origin.x = 0
subView.frame.origin.y = yPosition
// Adding the background color to the subViews
subView.backgroundColor = UIColor.blue
// Changing the yPosition and scrollViewHeight and adding a 20px margin for each subview in scrollView
yPosition += 200 + 20
scrollViewHeight += 200 + 20
// Adding labels to the subviews
let label1 = UILabel()
label1.text = "Label Added"
label1.textAlignment = .center
label1.textColor = UIColor.white
label1.frame.size.height = 30
label1.frame.size.width = phoneWidth
label1.frame.origin.x = 0
label1.frame.origin.y = 10
subView.addSubview(label1)
self.scrollView.addSubview(subView)
scrollView.contentSize = CGSize(width: phoneWidth, height: scrollViewHeight)
}
}

Adjust UIScrollView's contentSize with an embedded UIView?

I have a UIScroll view which contains only one subview. The subview called contentViewis an UIView. Here is what I did in viewDidLoad():
self.scrollView = UIScrollView()
self.view.addSubview(self.scrollView)
self.scrollView.backgroundColor = UIColor.yellowColor()
// pin all edges to the edges of the superview (self.view)
self.scrollView.snp_makeConstraints { (make) -> Void in
make.edges.equalTo(self.view)
}
// create contentView
self.contentView = UIView()
self.contentView.backgroundColor = UIColor.redColor()
self.scrollView.addSubview(self.contentView)
// pin the edges of the contentView to the scrollView
self.contentView.snp_makeConstraints { (make) -> Void in
make.edges.equalTo(self.scrollView)
}
let myView = UIView(frame: CGRectMake(100, 100, 20, 300))
myView.backgroundColor = UIColor.greenColor()
self.contentView.addSubview(myView)
The result is this:
There is ne red contentView as shown in the previous screen shot.
Next, I tried to adjust the size of the contenView in viewDidLayoutSubviews():
let newSize: CGSize = self.contentView.systemLayoutSizeFittingSize(UILayoutFittingCompressedSize)
println("newSize: \(newSize)")
The result is:
newSize: (0.0, 0.0)
How can I either setup the correct size of the contentView or set the correct size of the UIScrollView's contentSize?
have you tried setting the var contentSize: CGSize of the UIScrollView? The documentation says
Discussion The unit of size is points. The default size is CGSizeZero.
If you don't know the size of the content view, then I would recommend setting
scrollView.autoresizingMask = (UIViewAutoresizingFlexibleWidth| UIViewAutoresizingFlexibleHeight);
and
scrollView.autoresizesSubviews = YES;
for good measure.

Resources