Zooming an UIImageView that contains subviews - ios

I have an UIImageView object in UIScrollView(to zoom). In addition, UIImageView also contains subviews. I am using following function to zoom in UIImageView.
func viewForZooming(in scrollView: UIScrollView) -> UIView? {
return self.imageView
}
It works perfectly fine for UIImageView.
The problem is when zooming these subviews, it also zooms these subviews the same way. Is there a way to disable zooming for these subviews?

There are many ways to achieve this. I would say the easiest one is to simply back-scale your views. This is what I used as a demo:
class ViewController: UIViewController {
#IBOutlet private var scrollView: UIScrollView?
private var imageView: UIImageView?
private var annotations: [UIView] = [UIView]()
override func viewDidLoad() {
super.viewDidLoad()
let imageView = UIImageView(image: UIImage(named: "background"))
scrollView?.addSubview(imageView)
scrollView?.contentSize = imageView.bounds.size
scrollView?.maximumZoomScale = 5.0
self.imageView = imageView
scrollView?.delegate = self
applyAnnotationView({
let view = UIView(frame: CGRect(x: 0.0, y: 0.0, width: 10.0, height: 10.0))
view.backgroundColor = UIColor.red
view.layer.cornerRadius = view.bounds.width*0.5
return view
}(), at: CGPoint(x: 100.0, y: 100.0))
applyAnnotationView({
let view = UIView(frame: CGRect(x: 0.0, y: 0.0, width: 10.0, height: 10.0))
view.backgroundColor = UIColor.green
view.layer.cornerRadius = view.bounds.width*0.5
return view
}(), at: CGPoint(x: 200.0, y: 200.0))
}
func applyAnnotationView(_ view: UIView, at position: CGPoint) {
view.center = position
annotations.append(view)
imageView?.addSubview(view)
}
}
// MARK: - UIScrollViewDelegate
extension ViewController: UIScrollViewDelegate {
func scrollViewDidZoom(_ scrollView: UIScrollView) {
annotations.forEach { item in
item.transform = CGAffineTransform(scaleX: 1.0/scrollView.zoomScale, y: 1.0/scrollView.zoomScale)
}
}
func viewForZooming(in scrollView: UIScrollView) -> UIView? {
return imageView
}
}
Simply applying CGAffineTransform(scaleX: 1.0/scrollView.zoomScale, y: 1.0/scrollView.zoomScale) does all the work. It should be OK even when using constraints.

The UIView, which is inside the UIImageView, will always be the same size, but will be anchored to a specific point in the image (10% at the top and 10% at the left):
var item: UIView?
override func viewDidLoad() {
...
item = UIView(frame: CGRect(x: 0, y: 0, width: 200, height: 200))
setPosition()
item?.backgroundColor = UIColor.red
imageView.addSubview(item!)
...
}
func setPosition() {
let x = imageView.frame.size.width/10
let y = imageView.frame.size.height/10
item?.frame = CGRect(x: x, y: y, width: 200, height: 200)
}
func scrollViewDidZoom(_ scrollView: UIScrollView) {
...
setPosition()
...
}
Instead of 10%, you can use the exact figure, using the formula:
x = startPosition/startWidthImage*currentWidthImage

Related

Custom footerview not responding to my tap gesture recognizer label

So I've been at this for a few days and no luck. I have a a custom UIView that's made from a xib file and I am adding it to my footerView. The view is displayed but it is not responding to the gesture recognizer that I added to a label in the ViewController class. In the VC I create an instance of the UIView and added it to my tableView.
What's odd to is that when using the accessibility inspector it does read the labels
Here is the containerView
class AdviceCardWidgetContainerCurvedView: UIView {
struct Constants {
static let nibName = "ClariContainerView"
static let containerHeight: CGFloat = 169.0
static let curvedPercent: CGFloat = 0.2
}
let nibName = "ClariContainerView"
var clariView: UIView?
var clariTitle: String?
var clariQuestion: String?
#IBOutlet weak var clariTitleLabel: UILabel!
#IBOutlet weak var clariQuestionLabel: UILabel!
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
}
private func applyCurve(givenView view: UIView, curvedPercent: CGFloat) {
let shapeLayer = CAShapeLayer(layer: view.layer)
shapeLayer.path = self.pathForCurvedView(givenView: view, curvedPercent: curvedPercent).cgPath
shapeLayer.frame = view.bounds
view.layer.mask = shapeLayer
addSubview(view)
}
private func pathForCurvedView(givenView view: UIView, curvedPercent: CGFloat) -> UIBezierPath {
let path = UIBezierPath()
path.move(to: CGPoint(x: 0.0, y: 0.0))
path.addLine(to: CGPoint(x: 0.0, y: view.bounds.size.height))
path.addLine(to: CGPoint(x: UIScreen.main.bounds.width, y: view.bounds.size.height))
path.addLine(to: CGPoint(x: UIScreen.main.bounds.width, y: 0.0))
path.addQuadCurve(to: CGPoint(x: 0, y: 0), controlPoint: CGPoint(x: UIScreen.main.bounds.width / 2, y: view.bounds.size.height * curvedPercent))
path.close()
return path
}
private func commonInit() {
guard let clariView = loadViewFromNib() else { return }
clariView.frame = CGRect(x: 0, y: 0, width: UIScreen.main.bounds.width, height: containerHeight)
clariView.backgroundColor = .lightGray
applyCurve(givenView: clariView, curvedPercent: 0.1)
addSubview(clariView)
clariTitleLabel.text = "Ask TD Clari"
clariQuestionLabel.text = "What is my balance"
}
func loadViewFromNib() -> UIView? {
let bundle = Bundle(for: type(of: self))
let nib = UINib(nibName: Constants.nibName, bundle: bundle)
return nib.instantiate(withOwner: self, options: nil).first as? UIView
}
}
And this is what's inside my view controller
class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
#IBOutlet weak var tableView: UITableView!
let curvedView = AdviceCardWidgetContainerCurvedView()
override func viewDidLoad() {
super.viewDidLoad()
}
override func viewDidLayoutSubviews() {
curvedView.clariTitleLabel.accessibilityTraits = .button
curvedView.clariTitleLabel.isUserInteractionEnabled = true
curvedView.clariTitleLabel.backgroundColor = .red
curvedView.clariTitleLabel.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(footerLableTapped)))
tableView.tableFooterView = UIView(frame: CGRect(x: 0, y: 0, width: tableView.frame.width, height: 169))
tableView.tableFooterView?.addSubview(curvedView)
}
#objc func footerLableTapped() {
print("label tapped")
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 10
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell")!
cell.textLabel?.text = "1"
return cell
}
}
Alright, so I solved this problem after a week of trying. In the end adding a custom xib file to a footerView causes some errors and conflicts with autoLayout. Everything looks like its supposed to be, but the frames are essentially non existent for your added subviews like buttons and label and that's why they can't be tapped. Make sure you have a reference to your tableView from storyboard or how ever you created it.
override func viewDidLoad() {
super.viewDidLoad()
let remainingBottomFooterSpace = remaingBottomFooterViewSpace(for: tableView)
let footerView = UIView(frame: CGRect(x: 0, y: 0, width: tableView.frame.size.width, height: 169))
footerView.backgroundColor = .clear
applyCurve(givenView: footerView, curvedPercent: 0.04)
let remainingFooterView = UIView(frame: CGRect(x: 0, y: 169, width: tableView.frame.size.width, height: remainingBottomFooterSpace))
remainingFooterView.backgroundColor = .lightGray
let curve = UIView()
curve.translatesAutoresizingMaskIntoConstraints = false
curve.backgroundColor = .lightGray
let icon = UIImageView()
icon.translatesAutoresizingMaskIntoConstraints = false
icon.image = UIImage(named: "mytd_clari")
icon.contentMode = .scaleAspectFill
curved.addSubview(icon)
let button = UIButton()
button.translatesAutoresizingMaskIntoConstraints = false
button.isUserInteractionEnabled = true
button.setTitle("Ask", for: .normal)
button.titleLabel?.font = .systemFont(ofSize: 16.0, weight: .medium)
button.setTitleColor(.blue, for: .normal)
button.addTarget(self, action: #selector(footerLableTapped), for: .touchUpInside)
let questionLabel = UILabel()
questionLabel.translatesAutoresizingMaskIntoConstraints = false
questionLabel.text = "What is my balance"
questionLabel.textColor = .green
questionLabel.font = .systemFont(ofSize: 13, weight: .regular)
questionLabel.textAlignment = .center
curve.addSubview(clariQuestionLabel)
let buttonContainer = UIView(frame: CGRect(x: 0, y: 0, width: self.tableView.frame.size.width, height: 169))
curve.addSubview(buttonContainer)
buttonContainer.addSubview(button)
tableView.tableFooterView = footerView
tableView.tableFooterView?.addSubview(remainingFooterView)
tableView.tableFooterView?.addSubview(curve)
NSLayoutConstraint.activate([
footerView.trailingAnchor.constraint(equalTo: tableView.trailingAnchor),
footerView.leadingAnchor.constraint(equalTo: tableView.leadingAnchor)
])
NSLayoutConstraint.activate([
curve.widthAnchor.constraint(equalToConstant: tableView.frame.size.width),
curve.heightAnchor.constraint(equalToConstant: 169)
])
NSLayoutConstraint.activate([
icon.topAnchor.constraint(equalTo: curve.topAnchor, constant: 39),
icon.widthAnchor.constraint(equalToConstant: 40),
icon.heightAnchor.constraint(equalToConstant: 40),
icon.centerXAnchor.constraint(equalTo: curve.centerXAnchor),
])
NSLayoutConstraint.activate([
button.topAnchor.constraint(equalTo: icon.bottomAnchor),
button.centerXAnchor.constraint(equalTo: icon.centerXAnchor)
])
NSLayoutConstraint.activate([
label.topAnchor.constraint(equalTo: button.bottomAnchor),
label.centerXAnchor.constraint(equalTo: icon.centerXAnchor),
])
}
If you want to add the curve here are the methods:
func applyCurve(givenView view: UIView, curvedPercent: CGFloat) {
let shapeLayer = CAShapeLayer(layer: view.layer)
shapeLayer.path = self.pathForCurvedView(curvedPercent: curvedPercent).cgPath
shapeLayer.frame = view.bounds
view.layer.mask = shapeLayer
}
func pathForCurvedView(curvedPercent: CGFloat) -> UIBezierPath {
let path = UIBezierPath()
path.move(to: CGPoint(x: 0.0, y: 0.0))
path.addLine(to: CGPoint(x: 0.0, y: UIScreen.main.bounds.height))
path.addLine(to: CGPoint(x: UIScreen.main.bounds.width, y: UIScreen.main.bounds.height))
path.addLine(to: CGPoint(x: UIScreen.main.bounds.width, y: 0.0))
path.addQuadCurve(to: CGPoint(x: 0, y: 0), controlPoint: CGPoint(x: UIScreen.main.bounds.width / 2, y: UIScreen.main.bounds.height * curvedPercent))
path.close()
return path
}
and if you want to add the remaining footerView space, here is the func
func remaingBottomFooterViewSpace(for tableView: UITableView) -> CGFloat {
//Get content height & calculate new footer height
let cells = tableView.visibleCells
var height: CGFloat = 0
for i in 0..<cells.count {
height += cells[i].frame.height
}
height = tableView.bounds.height - ceil(height)
//If theh footer new height is negative, we make it 0 since we don't need extra footer view anymore
height = height > 0 ? height : 0
return height
}

UIScrollView with wider UIView not scrolling

I'm learning to use UIScrollView to make several pictures could scroll horizontally. These images are programmatically added as a subview to a UIView within UIScrollView. The 'Scrolling enabled' is also set true. However, when I run the app, the UIScrollView cannot scroll.
Here is my code.
class ViewController: UIViewController {
#IBOutlet weak var scrollView: UIScrollView!
#IBOutlet weak var scrollContentView: UIView!
var imageViews = [UIImageView]()
override func viewDidLoad() {
super.viewDidLoad()
let imageWidth: CGFloat = 200.0 / 768.0 * 1024.0
for i in 1...3 {
let image = UIImage(named: "image\(i)")
let imageView = UIImageView(image: image)
imageViews.append(imageView)
let x: CGFloat = CGFloat(i - 1) * (imageWidth + 10)
scrollContentView.addSubview(imageView)
print(imageView.frame)
imageView.frame = CGRect(x: x, y: 0, width: imageWidth, height: 200.0)
}
scrollContentView.frame = CGRect(x: 0.0, y: 0.0, width: imageWidth * 3 + 20.0, height: 200.0)
scrollView.contentSize = scrollContentView.frame.size
}
}
Instead of creating the scrollContentView in Interface Builder, create it programmatically.
Xcode won't complain anymore about missing contraints, and you will be able to play with the frame manually.
class ViewController: UIViewController {
#IBOutlet weak var scrollView: UIScrollView!
var scrollContentView: UIView!
var imageViews = [UIImageView]()
override func viewDidLoad() {
super.viewDidLoad()
scrollContentView = UIView()
scrollView.addSubview(scrollContentView)
let imageWidth: CGFloat = 200.0 / 768.0 * 1024.0
for i in 1...3 {
let image = UIImage(named: "image\(i)")
let imageView = UIImageView(image: image)
imageViews.append(imageView)
let x: CGFloat = CGFloat(i - 1) * (imageWidth + 10)
scrollContentView.addSubview(imageView)
print(imageView.frame)
imageView.frame = CGRect(x: x, y: 0, width: imageWidth, height: 200.0)
}
scrollContentView.frame = CGRect(x: 0.0, y: 0.0, width: imageWidth * 3 + 20.0, height: 200.0)
scrollView.contentSize = scrollContentView.frame.size
}
}

Swift 4 Pinch-to-Zoom with view on scrollview

If I pinch the simulator, it does not work! I want to zoom in and out )=
Here is my Code:
import UIKit
class ViewController: UIViewController, UIScrollViewDelegate {
#IBOutlet weak var scrollView: UIScrollView!
let onView = UIView()
override func viewDidLoad() {
super.viewDidLoad()
scrollView.delegate = self
scrollView.contentSize.width = 10000
scrollView.contentSize.height = 10000
onView.frame = CGRect(x: 0, y: 0, width: scrollView.frame.width, height: scrollView.frame.height)
scrollView.addSubview(onView)
for _ in 1...1000{
let x = arc4random_uniform(10000)
let y = arc4random_uniform(10000)
let btn = UIButton(frame: CGRect(x: Int(x), y: Int(y), width: Int(150), height: Int(100)))
btn.backgroundColor = .red
self.board.addSubview(btn)
}
scrollView.minimumZoomScale = 0.5
scrollView.maximumZoomScale = 10.0
scrollView.zoomScale = 1.0
}
func viewForZooming(in scrollView: UIScrollView) -> UIView? {
return scrollView
}
}

UIImageView as tableview header has initially wrong size?

I'm having trouble displaying an UIImageView as UITableView header.
When it's initially loaded it's not centered which it should be and its size is 600px? Then when I scroll it jumps to the correct position and adjusts itself to the correct size.
Can anyone tell me what is causing this and possibly how to fix it?
The problem:
How it should display initially:
NOTE:
When I print out the image height it's 600px wide but should initially be the same as the screenwidth. The UIImageView is set to Aspect Fill.
My setup:
#IBOutlet var tableView: UITableView!
#IBOutlet weak var tableImage:UIImageView!
var headerView: UIView!
var headerMaskLayer: CAShapeLayer!
private let kTableHeaderHeight: CGFloat = 260.0
private let kTableHeaderCutAway: CGFloat = 25.0
override func viewDidLoad() {
super.viewDidLoad()
headerView = tableView.tableHeaderView
tableView.tableHeaderView = nil
tableView.addSubview(headerView)
tableView.contentInset = UIEdgeInsets(top: kTableHeaderHeight, left: 0, bottom: 0, right: 0)
tableView.contentOffset = CGPoint(x: 0, y: -kTableHeaderHeight)
headerMaskLayer = CAShapeLayer()
headerMaskLayer.fillColor = UIColor.blackColor().CGColor
headerView.layer.mask = headerMaskLayer
updateHeaderView()
displayPoster(listBackdrop!)
//.... rest of code
}
func displayPoster (urlString: String)
{
if (urlString != ""){
let newUrl = urlString.stringByReplacingOccurrencesOfString("w500", withString: "w780")
self.tableImage.alpha = 0
let url = NSURL(string: newUrl)
// loading image with haneke lib
self.tableImage.hnk_setImageFromURL(url!)
UIView.animateWithDuration(1){
self.tableImage.alpha = 1.0
}
}
}
func scrollViewDidScroll(scrollView: UIScrollView) { updateHeaderView() }
func updateHeaderView(){
var headerRect = CGRect(x: (tableView.bounds.width-tableImage.bounds.width)/2, y: -kTableHeaderHeight, width: tableView.bounds.width, height: kTableHeaderHeight)
if tableView.contentOffset.y < -kTableHeaderHeight{
headerRect.origin.y = tableView.contentOffset.y
headerRect.size.height = -tableView.contentOffset.y
}
headerView.frame = headerRect
// the image cutaway
let path = UIBezierPath()
path.moveToPoint(CGPoint(x: 0, y: 0))
path.addLineToPoint(CGPoint(x: headerRect.width, y: 0))
path.addLineToPoint(CGPoint(x: headerRect.width, y: headerRect.height))
path.addLineToPoint(CGPoint(x: headerRect.width/2, y: headerRect.height-kTableHeaderCutAway))
path.addLineToPoint(CGPoint(x: 0, y: headerRect.height))
headerMaskLayer?.path = path.CGPath
}
The UIImageView constraints are setup as followed:
You are calling updateHeaderView() in your viewDidLoad(), which is before the view has had a chance to complete the layout. Try adding that to viewWillAppear() method.
I was facing the same issue while implementing stretchy headers. However calling updateHeaderView() in viewWillAppear() did not work for me.
It should be called from viewDidAppear() instead.

ScrollView, zoom doesn't work properly

I am creating an ios app using swift. In one of my ViewController, I have several UIView, each one responsible to show only a part of a picture (so I use a ScrollView, right?). I have subclass UIView to do that
import UIKit
class LittlePicture: UIView, UIScrollViewDelegate {
var scrollView = UIScrollView()
var imageView = UIImageView()
var image:UIImage!
init(frame: CGRect, image:UIImage, x:CGFloat, y:CGFloat, zoom: CGFloat) {
super.init(frame: frame)
scrollView.frame = self.frame
scrollView.delegate = self
scrollView.userInteractionEnabled = false
self.addSubview(scrollView)
setPhoto(image, x: x, y: y, zoom: zoom)
scrollView.addSubview(imageView)
}
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
func setPhoto(image:UIImage,x:CGFloat,y:CGFloat,zoom:CGFloat){
self.image = image
scrollView.contentSize = image.size
imageView.image = image
imageView.frame = CGRect(origin: CGPointZero, size: image.size)
scrollView.minimumZoomScale = 0.001
scrollView.maximumZoomScale = 100.0
scrollView.zoomScale = zoom
scrollView.contentOffset = CGPoint(x: x, y: y)
}
func viewForZoomingInScrollView(scrollView: UIScrollView) -> UIView? {
return imageView
}
}
Since the picture or the part of the picture to exhibit can change, I have implemented a function setPhoto. Problem is, when I call this function, ScrollView doesn't zoom anything (the scrollView.contentSize stays the same before and after calling scrollView.zoomScale = zoom)
In my UIViewController (it's just for the test...):
import UIKit
class ViewController: UIViewController {
var l:LittlePicture!
#IBAction func changePicture(sender:AnyObject){
l.setPhoto(UIImage(named: "a.jpg")!, x: 0, y: 0, zoom: 0.05)
}
override func viewDidLoad() {
super.viewDidLoad()
l=LittlePicture(frame: CGRect(x: 0, y: 0, width: 300, height: 300), image: UIImage(named: "a.jpg")!, x: 0, y: 0, zoom: 0.05)
view.addSubview(l)
}
}
When I click on the button, zoom get from 0.05 to 1.0. In fact, if I log scrollView.zoomScale, I obtain 0.05 but what I see on screen is a zoom equal to 1.0
Any idea? I am new at iOS development..

Resources