Why UIScrollView's content is not centered? - ios

I am embedding a UIImageView inside UIContentView to make it zoomable and panable (reference. The problem is that the image is not centered correctly. It looks like the entire image content is shifted out of top left corner:
While the printed content Offset is near zero: content offset is: (0.0, -20.0)
Here is my implementation:
import UIKit
class ZoomableImageView: UIScrollView {
private struct Constants {
static let minimumZoomScale: CGFloat = 0.5;
static let maximumZoomScale: CGFloat = 6.0;
}
// public so that delegate can access
public let imageView = UIImageView()
// gw: must be called to complete a setting
public func setImage(image: UIImage) {
imageView.image = image
imageView.bounds = CGRect(x: 0, y: 0, width: image.size.width, height: image.size.height)
self.contentSize = image.size
let scaleFitZoomScale: CGFloat = min(
self.frame.width / image.size.width ,
self.frame.height / image.size.height
)
// reset scale and offset on each resetting of image
self.zoomScale = scaleFitZoomScale
}
// MARK - constructor
init() {
// gw: there is no super.init(), you have to use this constructor as hack
super.init(frame: CGRect(x: 0, y: 0, width: 0, height: 0)) // gw: relies on autolayout constraint later
minimumZoomScale = Constants.minimumZoomScale
maximumZoomScale = Constants.maximumZoomScale
addSubview(imageView)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
ViewController:
class ViewController: UIViewController, UIScrollViewDelegate {
let zoomableImageView = ZoomableImageView()
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(zoomableImageView)
zoomableImageView.delegate = self
}
override func viewDidLayoutSubviews() {
zoomableImageView.frame = view.frame
let image = UIImage(imageLiteralResourceName: "kelly")
zoomableImageView.setImage(image: image)
}
func viewForZooming(in scrollView: UIScrollView) -> UIView? {
// print("scale factor is: \(scrollView.zoomScale)")
return zoomableImageView.imageView
}
func scrollViewDidZoom(_ scrollView: UIScrollView) {
print("scale factor is: \(scrollView.zoomScale)")
}
func scrollViewDidScroll(_ scrollView: UIScrollView) {
print("content offset is: \(scrollView.contentOffset)")
}
}
However, the centering of image works perfectly fine if I don't wrap the UIImageView and UIScrollView under the custom class:
class ViewController: UIViewController, UIScrollViewDelegate {
let imageView: UIImageView = {
let image = UIImage(imageLiteralResourceName: "kelly")
let imageView = UIImageView(image: image)
imageView.bounds = CGRect(x: 0, y: 0, width: image.size.width, height: image.size.height)
return imageView
} ()
let scrollView: UIScrollView = {
let scrollView = UIScrollView()
return scrollView
} ()
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
view.addSubview(scrollView)
scrollView.addSubview(imageView)
scrollView.delegate = self
self.scrollView.minimumZoomScale = 0.5;
self.scrollView.maximumZoomScale = 6.0;
}
override func viewDidLayoutSubviews() {
scrollView.frame = view.frame
if let size = imageView.image?.size {
scrollView.contentSize = size
}
}
func viewForZooming(in scrollView: UIScrollView) -> UIView? {
return imageView
}
}
Did I missed something in the first implementation?

In your ZoomableImageView class, you are setting bounds when it should be frame
So change:
imageView.bounds = CGRect(x: 0, y: 0, width: image.size.width, height: image.size.height)
to
imageView.frame = CGRect(x: 0, y: 0, width: image.size.width, height: image.size.height)

Related

How do I implement UIScrollView tiling with paging enabled?

I am trying to create an infinite scrolling paging UIScrollView I have been following the Advanced UIScrollView Techniques video from WWDC 2010 however I am unsure as to how to create tiling for a paging UIScrollView. I have been using this tutorial for guidance Infinite Paging along with this Stack Exchange answer. Is it possible to create such an effect in a paging UIScrollView or is tiling primarily used only in a continuous scrolling environment. Thank you for your help.
If you need infinite scrolling you can change element position after scroll.
very simple example and dirty code but I home I helped you:
import UIKit
class MainVC: UIViewController, UIScrollViewDelegate {
#IBOutlet var scrollView: UIScrollView!
var currentView: UIView?
var nextView: UIView?
var previousView: UIView?
override func viewDidLoad() {
super.viewDidLoad()
scrollView.isPagingEnabled = true
scrollView.delegate = self
generateViews()
}
func generateViews() {
let screenSize = UIScreen.main.bounds
currentView = UIView()
nextView = UIView()
previousView = UIView()
addPosition(cView: currentView!, nView: nextView!, pView: previousView!)
currentView?.backgroundColor = .red
nextView?.backgroundColor = .green
previousView?.backgroundColor = .blue
scrollView.addSubview(currentView!)
scrollView.addSubview(previousView!)
scrollView.addSubview(nextView!)
scrollView.contentSize = CGSize(width: screenSize.width * 3, height: screenSize.height)
}
func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
if scrollView.contentOffset.x >= (UIScreen.main.bounds.width * 2) {
addPosition(cView: nextView!, nView: previousView!, pView: currentView!)
} else if scrollView.contentOffset.x == 0 {
addPosition(cView: previousView!, nView: currentView!, pView: nextView!)
}
scrollView.contentOffset = CGPoint(x: UIScreen.main.bounds.width, y: 0)
}
func addPosition(cView: UIView, nView: UIView, pView: UIView) {
let screenSize = UIScreen.main.bounds
cView.frame = CGRect(origin: CGPoint(x: screenSize.width, y: 0), size: screenSize.size)
nView.frame = CGRect(origin: CGPoint(x: screenSize.width * 2, y: 0), size: screenSize.size)
pView.frame = CGRect(origin: CGPoint(x: 0, y: 0), size: screenSize.size)
currentView = cView
nextView = nView
previousView = pView
}
}

Zoom on UIView contained in UIScrollView

I have some trouble handling zoom on UIScrollView that contains many subviews. I already saw many answers on SO, but none of them helped me.
So, basically what I'm doing is simple : I have a class called UIFreeView :
import UIKit
class UIFreeView: UIView, UIScrollViewDelegate {
var mainUIView: UIView!
var scrollView: UIScrollView!
var arrayOfView: [UIView]?
override init(frame: CGRect) {
super.init(frame: frame)
self.setupView()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
fileprivate func setupView() {
// MainUiView
self.mainUIView = UIView(frame: self.frame)
self.addSubview(mainUIView)
// ScrollView
self.scrollView = UIScrollView(frame: self.frame)
self.scrollView.delegate = self
self.addSubview(self.scrollView)
}
func reloadViews(postArray:[Post]?) {
if let postArray = postArray {
print("UIFreeView::reloadVIew.postArraySize = \(postArray.count)")
let size: CGFloat = 80.0
let margin: CGFloat = 20.0
scrollView.contentSize.width = (size * CGFloat(postArray.count))+(margin*CGFloat(postArray.count))
scrollView.contentSize.height = (size * CGFloat(postArray.count))+(margin*CGFloat(postArray.count))
for item in postArray {
let view = buildPostView(item)
self.scrollView.addSubview(view)
}
}
}
fileprivate func buildPostView(_ item:Post) -> UIView {
// Const
let size: CGFloat = 80.0
let margin: CGFloat = 5.0
// Var
let view = UIView()
let textView = UITextView()
let backgroundImageView = UIImageView()
// Setup view
let x = CGFloat(UInt64.random(lower: UInt64(0), upper: UInt64(self.scrollView.contentSize.width)))
let y = CGFloat(UInt64.random(lower: UInt64(0), upper: UInt64(self.scrollView.contentSize.height)))
view.frame = CGRect(x: x,
y: y,
width: size,
height: size)
// Setup background view
backgroundImageView.frame = CGRect(x: 0,
y: 0,
width: view.frame.size.width,
height: view.frame.size.height)
var bgName = ""
if (item.isFromCurrentUser) {
bgName = "post-it-orange"
} else {
bgName = "post-it-white"
}
backgroundImageView.contentMode = .scaleAspectFit
backgroundImageView.image = UIImage(named: bgName)
view.addSubview(backgroundImageView)
// Setup text view
textView.frame = CGRect(x: margin,
y: margin,
width: view.frame.size.width - margin*2,
height: view.frame.size.height - margin*2)
textView.backgroundColor = UIColor.clear
textView.text = item.content
textView.isEditable = false
textView.isSelectable = false
textView.isUserInteractionEnabled = false
view.addSubview(textView)
let gestureRecognizer = UIPanGestureRecognizer(target: self, action: #selector(handlePan))
view.addGestureRecognizer(gestureRecognizer)
return view
}
func viewForZooming(in scrollView: UIScrollView) -> UIView? {
return self.scrollView
}
func handlePan(_ gestureRecognizer: UIPanGestureRecognizer) {
if gestureRecognizer.state == .began || gestureRecognizer.state == .changed {
let translation = gestureRecognizer.translation(in: self.scrollView)
// note: 'view' is optional and need to be unwrapped
gestureRecognizer.view!.center = CGPoint(x: gestureRecognizer.view!.center.x + translation.x, y: gestureRecognizer.view!.center.y + translation.y)
gestureRecognizer.setTranslation(CGPoint.zero, in: self.scrollView)
}
}
/*
// Only override draw() if you perform custom drawing.
// An empty implementation adversely affects performance during animation.
override func draw(_ rect: CGRect) {
// Drawing code
}
*/
}
This class is working perfectly, I can scroll through all my views, but I can't zoom-in and zoom-out in order to see less/more views on my screen.
I think the solution is simple, but I can't seem to find a real solution for my problem ..
Thanks !

How to detect image is portrait and landscape mode about imageView zoom

I am practicing how to use the imageView in Swift.
I recently try to do image zooming in UIScrollView, but my problem is that I don't know how to check the portrait and landscape image size.
Portrait image in my ImageView will look strange.
What am I doing wrong and how can I set different imageView size in a scrollView?
When I set constrain in imageView, the imageview will can't zoom. This is my code:
class imageViewController: UIViewController, UIScrollViewDelegate {
var url:String = ""
var id:String = ""
var scrollView:UIScrollView = UIScrollView()
var imageView:UIImageView = UIImageView()
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
}
override func viewDidLoad() {
super.viewDidLoad()
self.automaticallyAdjustsScrollViewInsets = false
imageView.contentMode = .scaleAspectFit
imageView = UIImageView(frame: CGRect(x: 0, y: UIScreen.main.bounds.height*0.2248, width: UIScreen.main.bounds.width, height:UIScreen.main.bounds.height*0.3748))
imageFromUrl(imageView: imageView, url: url, id: id,isResize: false)
scrollView = UIScrollView(frame: CGRect(x: 0, y: 0, width: view.frame.size.width, height: view.frame.size.height))
scrollView.contentSize.width = UIScreen.main.bounds.width
scrollView.contentSize.height = UIScreen.main.bounds.height*0.3748
scrollView.minimumZoomScale = 1
scrollView.maximumZoomScale = 5
scrollView.delegate = self
scrollView.addSubview(imageView)
view.addSubview(scrollView)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
// MARK: - UIScrollViewDelegate
func viewForZooming(in scrollView: UIScrollView) -> UIView? {
return imageView
}
}
[portrait image like this]

View does not stick on top (does not change position)

I am trying to stick the UIView on top while scrolling. Tried using max(x:T, y:T) method as suggested here on stackOverflow but it does not work. I manage to detect when the scrollView should be re-positioned but updating frame does not have any affect.
import UIKit
class ViewController: UIViewController, UIScrollViewDelegate{
#IBOutlet var objectView: UIView!
#IBOutlet var scrollView: UIScrollView!
//var originalOffsetY : CGFloat!
//var originalOffestX : CGFloat!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
let size = CGSize.init(width: self.view.frame.width, height: self.view.frame.height*2)
scrollView.contentSize = size
scrollView.backgroundColor = UIColor.blueColor()
objectView.backgroundColor = UIColor.whiteColor()
scrollView.delegate = self
scrollView.addSubview(objectView)
}
// override func viewWillAppear(animated: Bool) {
// originalOffsetY = objectView.frame.origin.y
// originalOffestX = objectView.frame.origin.x
// }
func scrollViewDidScroll(scrollView: UIScrollView) {
print("ScrollView \(scrollView.contentOffset.y)")
print("ObjectView \(objectView.frame.origin.y)")
let location = CGPointMake(0, scrollView.contentOffset.y)
let size = objectView.frame.size
if scrollView.contentOffset.y >= objectView.frame.origin.y {
objectView.frame = CGRect.init(origin: location, size: size)
print("Detected \(objectView.frame)")
}
// var newRect = objectView.frame
// newRect.origin.y = max(originalOffsetY!, scrollView.contentOffset.y)
// objectView.frame = newRect
}
}
The condition is matched successfully as can be seen here in Logs . But the view remains unchanged. I require the objectView to scroll a bit.. from around y=270 to 0 .. but not beyond it.
You need to keep track of the original position of the view within the scroll view, and calculate using that value, here's a very simplified example:
class StickyScrollViewController: UIViewController {
var stickyView = UIView(frame: CGRect(x: 0, y: 100, width: 300, height: 100))
var originalStickyOrigin: CGPoint = CGPoint(x: 0, y: 100)
func scrollViewDidScroll(_ scrollView: UIScrollView) {
var nextYOffset = max(originalStickyOrigin.y, scrollView.contentOffset.y)
stickyView.frame = CGRect(x: 0, y: nextYOffset, width: 300, height: 100)
}
}

Rotating UIView in UIScrollview Swift

When I rotate the view in the scrollview it moves out of the scrollview and disappears completely after some rotation/zoom gestures. It works fine as long as the zoom scale is 1.
What do I have to do with my code to avoid this?
import UIKit
class ViewController: UIViewController, UIScrollViewDelegate {
let scrollView = UIScrollView(frame: UIScreen.mainScreen().bounds)
let rotationView = UIView()
override func viewDidLoad() {
super.viewDidLoad()
let imageView = UIImageView()
view.addSubview(scrollView)
scrollView.delegate = self
scrollView.minimumZoomScale = 0.5
scrollView.maximumZoomScale = 2
let mapImage = UIImage(named: "BMS2_300.jpg")
imageView.frame = CGRect(origin: CGPoint(x: 0, y: 0), size:mapImage!.size)
imageView.image = mapImage
let rotationViewframe = CGRectMake(0, 0, imageView.frame.size.width, imageView.frame.size.height)
rotationView.frame = rotationViewframe
rotationView.addSubview(imageView)
//rotationView.sizeToFit()
scrollView.addSubview(rotationView)
scrollView.contentSize = CGSize(width: rotationView.bounds.width, height: rotationView.bounds.height)
scrollView.bringSubviewToFront(rotationView)
scrollView.contentOffset = CGPoint(x: rotationView.frame.width/2, y: rotationView.frame.height/2)
let mapRotGestureRecognizer = UIRotationGestureRecognizer(target: self, action: #selector(ViewController.rotateMap(_:)))
rotationView.addGestureRecognizer(mapRotGestureRecognizer)
}
func rotateMap(sender: UIRotationGestureRecognizer) {
let radians = sender.rotation
if let senderView = sender.view {
senderView.transform = CGAffineTransformRotate(senderView.transform, radians)
sender.rotation = 0
}
}
func viewForZoomingInScrollView(scrollView: UIScrollView) -> UIView? {
return self.rotationView
}
func scrollViewDidEndZooming(scrollView: UIScrollView, withView view: UIView?, atScale scale: CGFloat) {
scrollView.contentSize = rotationView.frame.size
}
The solution is to add an extra uiview under the scrollview, so the new holderView is a subview of the scrollview and the rotatonView a subview of the holderView.

Resources