ScrollView, zoom doesn't work properly - ios

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..

Related

Subclass UINavigationBar SWIFT

I want to pose my problem with the hope that someone can help me:
I created a subclass of UINavigationBar, adding an image as a background.
here is the code:
class NavigationBarN: UINavigationBar {
let customHeight: CGFloat = UIScreen.main.bounds.height / 8
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
translatesAutoresizingMaskIntoConstraints = true
self.barStyle = UIBarStyle.black
self.tintColor = .white
}
override init(frame: CGRect) {
super.init(frame: frame)
translatesAutoresizingMaskIntoConstraints = true
self.barStyle = UIBarStyle.black
self.tintColor = .white
}
override func sizeThatFits(_ size: CGSize) -> CGSize {
return CGSize(width: UIScreen.main.bounds.width, height: customHeight)
}
var i = 0
override open func layoutSubviews() {
super.layoutSubviews()
frame = CGRect(x: frame.origin.x, y: 0, width: frame.size.width, height: customHeight) // problem here
for subview in self.subviews {
var stringFromClass = NSStringFromClass(subview.classForCoder)
if stringFromClass.contains("BarBackground") {
subview.frame = CGRect(x: 0, y: 0, width: self.frame.width, height: self.frame.height)
let imageViewBackground = UIImageView(frame: CGRect(x: 0, y: 0, width: frame.size.width, height: customHeight))
imageViewBackground.image = UIImage(named: “Image”1)
imageViewBackground.contentMode = .scaleToFill
subview.addSubview(imageViewBackground)
}
stringFromClass = NSStringFromClass(subview.classForCoder)
if stringFromClass.contains("BarContent") {
let centerY = subview.frame.heigh
subview.frame = CGRect(x: subview.frame.origin.x, y: centerY, width: subview.frame.width, height: subview.frame.height)
}
}
}
/*
// Only override draw() if you perform custom drawing.
// An empty implementation adversely affects performance during animation.
override func draw(_ rect: CGRect) {
// Drawing code
}
*/
}
Up to iOS 11, it works without problems since iOS 12 the problems begin, the subclass enters an infinite loop. However the infinite loop occurs only when I try to initialize a new VC through the "navigationController? .PushViewController" function, otherwise it works.
The line of code causing the loop apparently is:
frame = CGRect(x: frame.origin.x, y: 0, width: frame.size.width, height: customHeight)
But removing it the navigation bar is not created correctly.
Anyone have any solution?
Thank You
You shouldn't change your frame in layoutSubviews – you should only be laying out your subviews. Changing the size of the frame in layoutSubviews couples the layout and sizing of the view, which you shouldn't do.
It's likely that under the hood, UINavigationBar is calling setNeedsLayout when its frame gets set.

Zooming an UIImageView that contains subviews

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

Unable to properly animate the frame of a subview within a parent view

I am trying to animate the frame change of a subview within a view. Within the animation block I am setting the size of the subview to half its original width and height. However, when I run the code, the subview unexpectedly started with a larger size and shrunk to its original size instead of becoming half its original size. I am using the code below:
My custom view:
import UIKit
class TestView: UIView {
var testSubview: UIView!
override init(frame: CGRect) {
super.init(frame: frame)
backgroundColor = UIColor.yellow
testSubview = UIView(frame: .zero)
testSubview.backgroundColor = UIColor.red
addSubview(testSubview)
}
required init?(coder aDecoder: NSCoder) {
fatalError()
}
override func layoutSubviews() {
super.layoutSubviews()
testSubview.frame = CGRect(x: 0, y: 0, width: 100, height: 100)
}
func testAnimate() {
UIView.animate(withDuration: 3, delay: 0, options: [], animations: { [unowned self] in
self.testSubview.bounds.size = CGSize(width: 50, height: 50)
}, completion: nil)
}
}
My view controller:
import UIKit
class TestViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
let testView = TestView()
view.addSubview(testView)
testView.frame = CGRect(x: 0, y: 0, width: 200, height: 200)
testView.center = view.center
testView.layoutIfNeeded()
testView.testAnimate()
}
}
Am I doing something wrongly?
The problem is that your view gets laid out two times, you can check that by printing a debug message inside layoutSubviews().
I would suggest something like this, setting a size variable and use that in both layoutSubviews() and testAnimate():
class TestView: UIView {
var testSubview: UIView!
var mySize = CGSize(width: 100, height: 100)
override init(frame: CGRect) {
super.init(frame: frame)
backgroundColor = UIColor.yellow
testSubview = UIView(frame: .zero)
testSubview.backgroundColor = UIColor.red
addSubview(testSubview)
}
required init?(coder aDecoder: NSCoder) {
fatalError()
}
override func layoutSubviews() {
super.layoutSubviews()
testSubview.frame = CGRect(x: 0, y: 0, width: mySize.width, height: mySize.height)
}
func testAnimate() {
UIView.animate(withDuration: 3, delay: 0, options: [], animations: { [unowned self] in
self.mySize = CGSize(width: 50, height: 50)
self.testSubview.bounds.size = self.mySize
}, completion: nil)
}
}
EDIT: Reformulating my whole answer because there are many things I feel should be changed here.
first of all the difference between setting the bounds of a view and it's frame.
let view = UIView()
view.frame = CGRect(x: 0, y: 0, width: 200, height: 200) // This will set the position and size this view will take relative to it's future parents view
view.bounds = CGRect(x: 50, y: 50, width: 100, height: 100) // This will set position and size of it's own content (i.e. it's background) relative to it's own view
Then comes your testView class variables. Unless you are certain that a variable won't be nil don't use UIView! as the typing, you would end up missing some initialisers and thus your class would not work consistently.
Consider init methods as default values for your variables and you can simply alter them afterwards.
import UIKit
class TestView: UIView {
var testSubview: UIView
override init(frame: CGRect) {
// Initialise your variables before calling super
testSubview = UIView(frame: CGRect(x: 0, y: 0, width: 100, height: 100))
testSubview.backgroundColor = UIColor.red
super.init(frame: frame)
// Once super is called you can start using class methods or variable such as addSubview or backgroundColor
backgroundColor = UIColor.yellow
addSubview(testSubview)
}
required init?(coder aDecoder: NSCoder) {
testSubview = UIView(frame: CGRect(x: 0, y: 0, width: 100, height: 100))
testSubview.backgroundColor = UIColor.blue
super.init(coder: aDecoder)
backgroundColor = UIColor.yellow
addSubview(testSubview)
}
}
now finally to the animation part
import UIKit
class TestView: UIView {
var testSubview: UIView
func testAnimation() {
UIView.animate(withDuration: 3, animations: {
self.testSubview.transform = CGAffineTransform(scaleX: 0.5, y: 0.5) // Simply make the view half its size over 3 seconds
}) { (_) in
UIView.animate(withDuration: 3, animations: {
self.testSubview.transform = CGAffineTransform(scaleX: 1, y: 1) // Upon completion, resize the view back to it's original size
})
}
}
}
or if you really want to use CGRect to do animation on it's position
import UIKit
class TestView: UIView {
var testSubview: UIView
func testAnimation() {
UIView.animate(withDuration: 3, animations: {
self.testSubview.frame = CGRect(x: 0, y: 0, width: 50, height: 50) // use frame not bounds to resize and reposition the frame
}) { (_) in
UIView.animate(withDuration: 3, animations: {
self.testSubview.frame = CGRect(x: 0, y: 0, width: 100, height: 100)
})
}
}
}
you can test the effect of changing the bounds attribute as such
import UIKit
class ViewController: UIViewController {
var testView: TestView!
override func viewDidLoad() {
testView = TestView(frame: CGRect(origin: .zero, size: CGSize(width: 200, height: 200)))
testView.bounds = CGRect(x: 50, y: 50, width: 100, height: 100)
testView.clipsToBounds = true
view.addSubview(testView)
super.viewDidLoad()
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
testView.testAnimation()
}
}
simply play around with clipsToBounds, testView.bounds, testView.subView.frame and testView = TestView(frame: CGRect(x:y:width:height) to see all of this

UIScrollView with paging enabled swipe issue

I have a UIScrollView with paging enabled. All works good but sometimes when I scroll to the next image, 1 pixel from the last image is still displayed.
as you can see on the very left of the image, there is 1 pixel vertical line that is from the image before it. Meaning that the image was not swiped completely.
I tried a lot but couldn't find the problem.
Here is my code:
import Foundation
import UIKit
import SwiftyGif
class MBSPagingScrollView: UIScrollView, UIScrollViewDelegate {
var scrollView = UIScrollView()
var pageControl = UIPageControl()
var imagesArray = [String]()
override init (frame : CGRect) {
super.init(frame : frame)
}
convenience init () {
self.init(frame:CGRect.zero)
}
required init(coder aDecoder: NSCoder) {
fatalError("This class does not support NSCoding")
}
init(frame : CGRect, imagesarray : [String]){
super.init(frame : frame)
imagesArray = imagesarray
scrollView.delegate = self
scrollView.isPagingEnabled = true
scrollView.frame = CGRect(x: 0, y: 0, width: self.frame.size.width, height: self.frame.size.height)
scrollView.showsHorizontalScrollIndicator = false
self.addSubview(scrollView)
pageControl.frame = CGRect(x: 0, y: self.frame.size.height - 50, width: self.frame.size.width, height: 50)
pageControl.numberOfPages = imagesArray.count
pageControl.currentPage = 0
pageControl.tintColor = UIColor.red
pageControl.pageIndicatorTintColor = UIColor.black
pageControl.currentPageIndicatorTintColor = MainOrangeColor
self.addSubview(pageControl)
scrollView.contentSize = CGSize(width:scrollView.frame.size.width * CGFloat(imagesArray.count),height: scrollView.frame.size.height)
pageControl.addTarget(self, action: #selector(changePage(sender:)), for: UIControlEvents.valueChanged)
for (index, imageName) in imagesArray.enumerated() {
var imageView = UIImageView()
if imageName.hasSuffix(".gif") {
let gifManager = SwiftyGifManager(memoryLimit:30)
let gif = UIImage(gifName: imageName)
imageView = UIImageView(gifImage: gif, manager: gifManager)
}
else {
imageView = UIImageView(image: UIImage(named: imageName))
imageView.contentMode = UIViewContentMode.scaleToFill
}
imageView.frame = CGRect(x: CGFloat(index) * self.frame.size.width, y: 0, width: self.frame.size.width, height: self.frame.size.height)
scrollView.addSubview(imageView)
}
}
func changePage(sender: AnyObject) -> () {
let x = CGFloat(pageControl.currentPage) * scrollView.frame.size.width
scrollView.setContentOffset(CGPoint(x:x, y:0), animated: true)
}
func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
let pageNumber = round(scrollView.contentOffset.x / scrollView.frame.size.width)
pageControl.currentPage = Int(pageNumber)
}
}
Any help is much appreciated.
Thanks a lot!
Try and set clipsToBounds = true for all the imageviews.

Add subviews on Customize UIView works not as expect

I subclass a class of UIView called:AURTabView. Then I drag 3 views(white background) to storyboard to auto layout them with 1/3 screen width of each. Then i add a UIButton on the init method, it works well so far as below:
Then i want to add another two UIViews on to them. The weird thing is the first and third AUTRabView works as expect, but the middle one doesn't.
This is really weird. I checked UIView hierarchy like below:
Any point?
Here is the code:
class AURTabView: UIView {
let tabButton = UIButton()
let smallCircle = UIView()
let largeCircle = UIView()
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
self.addSubview(tabButton)
self.addSubview(smallCircle)
self.addSubview(largeCircle)
}
override func layoutSubviews() {
super.layoutSubviews()
let height = self.frame.height
tabButton.frame = CGRect(x: (self.frame.width-height)/2, y: 0, width: height, height: height)
tabButton.backgroundColor = UIColor.greenColor()
smallCircle.frame = CGRect(x: CGRectGetMidX(self.frame)-2.5, y: height-10-8, width: 5, height: 5)
smallCircle.backgroundColor = UIColor.redColor()
largeCircle.frame = CGRect(x: CGRectGetMidX(self.frame)-5, y: height-8, width: 10, height: 10)
largeCircle.backgroundColor = UIColor.redColor()
print(smallCircle)
print(largeCircle)
}
override func drawRect(rect: CGRect) {
tabButton.layer.cornerRadius = tabButton.frame.width/2
}
}
Use this
import UIKit
class AURTabView: UIView {
let tabButton = UIButton()
let smallCircle = UIView()
let largeCircle = UIView()
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
self.addSubview(tabButton)
self.addSubview(smallCircle)
self.addSubview(largeCircle)
}
override func layoutSubviews() {
super.layoutSubviews()
let height = self.frame.height
tabButton.frame = CGRect(x: (self.frame.width-height)/2, y: 0, width: height, height: height)
tabButton.backgroundColor = UIColor.greenColor()
smallCircle.frame = CGRect(x: self.frame.width/2 - 2.5, y: height-10-8, width: 5, height: 5)
smallCircle.backgroundColor = UIColor.blackColor()
largeCircle.frame = CGRect(x: self.frame.width/2 - 5, y: height-8, width: 10, height: 10)
largeCircle.backgroundColor = UIColor.redColor()
print(smallCircle)
print(largeCircle)
}
override func drawRect(rect: CGRect) {
tabButton.layer.cornerRadius = tabButton.frame.width/2
}
}

Resources