I am trying to add a Page Control on top of my UIScrollView Custom class. Everything is wokring fine. But whenever I slide the scrollView, the Page Control will slide along the first Image as well.
Here is my code:
import Foundation
import UIKit
import SwiftyGif
class MBSPagingScrollView: 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
self.isPagingEnabled = true
self.showsHorizontalScrollIndicator = false
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)
self.addSubview(imageView)
}
self.contentSize = CGSize(width:self.frame.size.width * CGFloat(imagesArray.count),height: self.frame.size.height)
configurePageControl()
pageControl.addTarget(self, action: #selector(self.changePage(sender:)), for: UIControlEvents.valueChanged)
}
func configurePageControl() {
self.pageControl.frame = CGRect(x: 0, y: self.frame.size.height - 50, width: self.frame.size.width, height: 50)
self.pageControl.numberOfPages = imagesArray.count
self.pageControl.currentPage = 0
self.pageControl.tintColor = UIColor.red
self.pageControl.pageIndicatorTintColor = UIColor.black
self.pageControl.currentPageIndicatorTintColor = MainOrangeColor
self.addSubview(pageControl)
}
// // MARK : TO CHANGE WHILE CLICKING ON PAGE CONTROL
func changePage(sender: AnyObject) -> () {
let x = CGFloat(pageControl.currentPage) * self.frame.size.width
self.setContentOffset(CGPoint(x:x, y:0), animated: true)
}
func selfDidEndDecelerating(_ scrollView: UIScrollView) {
let pageNumber = round(self.contentOffset.x / self.frame.size.width)
pageControl.currentPage = Int(pageNumber)
}
}
And here are images of the problem happening
As you can see the Page Control is sliding with the first image. I need it to stay in the center.
Any help would be appreciated!
I think you can do that by going to Storyboard, and from the left menu of the UI Builder, drag your page control down and put it out of your scroll view and under it, like in the following picture:
Then you'll need to add the suitable constraints to place your page control properly.
Good luck!
If you don’t want it to scroll... don’t add it to the scroll View.
Just add the page control to the self.view instead of to the scroll view.
Related
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)
I am trying to learn the basics of IOS development and Ive been experimenting with different views.
I tried to make a scroll view that display an array of images which scrolls properly but none of the images appear?
import UIKit
class ViewController: UIViewController {
var imagesArray = [UIImage]()
let scrollView: UIScrollView = {
let scroll = UIScrollView()
scroll.isPagingEnabled = true
scroll.showsVerticalScrollIndicator = false
scroll.showsHorizontalScrollIndicator = false
scroll.frame = CGRect(x: 0, y: 0, width: UIScreen.main.bounds.width, height: UIScreen.main.bounds.height)
return scroll
}()
override func viewDidLoad() {
super.viewDidLoad()
setScrollView()
}
func setScrollView(){
self.view.addSubview(scrollView)
imagesArray = [UIImage(named:"gamerrr"),UIImage(named:"family"),UIImage(named:"gamerrr"),UIImage(named:"family"),UIImage(named:"family")] as! [UIImage]
setUpImages(imagesArray)
}
func setUpImages(_ images:[UIImage]){
for i in 0..<images.count {
// container of each image
let imageView = UIImageView()
// shifting to next image
let xPos = UIScreen.main.bounds.width * CGFloat(i)
// width and height of the screen
imageView.frame = CGRect(x: xPos, y: 0, width: scrollView.frame.width, height: scrollView.frame.height)
imageView.contentMode = .scaleAspectFit
// shifting to next image
scrollView.contentSize.width = scrollView.frame.width * CGFloat(i+1)
scrollView.addSubview(imageView)
}
}
}
You may need to set the image
let imageView = UIImageView(image:images[i])
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 !
I am trying to implement a scroll view with page control to display images in iOS using Swift, to get an output like this:
The scroll view here is a part of another view controller.
I googled but got no help to get the required output.
Can anyone please help me?
The answer is a bit late. So, may be helpful for someone else:
Swift 3x:
First of all, give the delegation like this:
class YOUR_VIEW_CONTROLLER: UIViewController, UIScrollViewDelegate
Now connect the outlets of UIScrollView and UIPageControl like this:
#IBOutlet weak var scrollView: UIScrollView!
#IBOutlet weak var pageControl: UIPageControl!
NOTE:I'm using colours array. You can use image array instead.
var colors:[UIColor] = [UIColor.red, UIColor.blue, UIColor.green, UIColor.yellow]
var frame: CGRect = CGRect(x:0, y:0, width:0, height:0)
Now just copy and paste the below code in your class:
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
configurePageControl()
self.view.bringSubview(toFront: pageControl)
scrollView.delegate = self
self.view.addSubview(scrollView)
for index in 0..<4 {
frame.origin.x = self.scrollView.frame.size.width * CGFloat(index)
frame.size = self.scrollView.frame.size
let subView = UIView(frame: frame)
subView.backgroundColor = colors[index]
self.scrollView.addSubview(subView)
}
self.scrollView.isPagingEnabled = true
self.scrollView.contentSize = CGSize(width:self.scrollView.frame.size.width * 4,height: self.scrollView.frame.size.height)
pageControl.addTarget(self, action: #selector(self.changePage(sender:)), for: UIControlEvents.valueChanged)
}
func configurePageControl() {
// The total number of pages that are available is based on how many available colors we have.
self.pageControl.layer.zPosition = 1
self.pageControl.numberOfPages = colors.count
self.pageControl.currentPage = 0
self.pageControl.tintColor = UIColor.red
self.pageControl.pageIndicatorTintColor = UIColor.black
self.pageControl.currentPageIndicatorTintColor = UIColor.green
self.view.addSubview(pageControl)
}
// MARK : TO CHANGE WHILE CLICKING ON PAGE CONTROL
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)
}
I made a simple custom view in Swift which has a background image view with a photo and some labels in front. I then added the custom view to my storyboard and it displays well, I can see the background image and labels.
But when I run my app on my device the image view doesn't show immediately, if I navigate to another view then back, it is displayed. I only have a timer which scheduled some tasks in background in my scene. Did I miss anything here?
Here is the code of my custom view
import UIKit
#IBDesignable class GaugeView: UIImageView {
#IBOutlet weak var speedLabel: UILabel!
let circlePathLayer = CAShapeLayer()
var circleRadius: CGFloat = 50.0
var speed: CGFloat {
get {
return circlePathLayer.strokeEnd
}
set {
speedLabel.text = String(format: "%.2f", newValue)
if newValue < 20.0 {
circlePathLayer.strokeColor = UIColor.blueColor().CGColor
} else if newValue >= 25.0 {
circlePathLayer.strokeColor = UIColor.redColor().CGColor
} else {
circlePathLayer.strokeColor = UIColor.yellowColor().CGColor
}
if (newValue > 50) {
circlePathLayer.strokeEnd = 1
} else if (newValue < 0) {
circlePathLayer.strokeEnd = 0
} else {
circlePathLayer.strokeEnd = newValue / 49.9
}
}
}
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
xibSetup()
}
override init(frame: CGRect) {
super.init(frame: frame)
xibSetup()
}
override func layoutSubviews() {
super.layoutSubviews()
circleRadius = bounds.width / 2 - 2.6
circlePathLayer.frame = bounds
circlePathLayer.path = circlePath().CGPath
}
// Our custom view from the XIB file
var view: UIView!
let nibName = "GaugeView"
func xibSetup() {
view = loadViewFromNib()
// use bounds not frame or it'll be offset
view.frame = bounds
// Make the view stretch with containing view
view.autoresizingMask = UIViewAutoresizing.FlexibleWidth | UIViewAutoresizing.FlexibleHeight
// Adding custom subview on top of our view (over any custom drawing > see note below)
addSubview(view)
// Configure speed circle layer
configure()
}
func configure() {
speed = 0.0
circlePathLayer.frame = bounds
circlePathLayer.lineWidth = 6
circlePathLayer.fillColor = UIColor.clearColor().CGColor
circlePathLayer.strokeColor = UIColor.blueColor().CGColor
layer.addSublayer(circlePathLayer)
backgroundColor = UIColor.whiteColor()
}
func circleFrame() -> CGRect {
var circleFrame = CGRect(x: 0, y: 0, width: 2*circleRadius, height: 2*circleRadius)
circleFrame.origin.x = CGRectGetMidX(circlePathLayer.bounds) - CGRectGetMidX(circleFrame)
circleFrame.origin.y = CGRectGetMidY(circlePathLayer.bounds) - CGRectGetMidY(circleFrame)
return circleFrame
}
func circlePath() -> UIBezierPath {
let center: CGPoint = CGPointMake(CGRectGetMidX(circlePathLayer.bounds), CGRectGetMidY(circlePathLayer.bounds))
let start = CGFloat(1.33 * M_PI_2)
let end = CGFloat( 1.64 * M_2_PI)
return UIBezierPath(arcCenter: center, radius: circleRadius, startAngle: start, endAngle: end, clockwise: true)
}
func loadViewFromNib() -> UIView {
let bundle = NSBundle(forClass: self.dynamicType)
let nib = UINib(nibName: nibName, bundle: bundle)
let view = nib.instantiateWithOwner(self, options: nil)[0] as! UIView
return view
}
}
Then I added the custom view to my storyboard and navigate to the scene programatically with performSegueWithIdentifier("dashboardSegue", sender: nil). I just noticed not only the custome view, but two progress bar on the scene are not displayed as well.
It's better show your code, but i am guessing your timer stuck the main UI thread. When you navigate to another view, your timer.invalidate() was called. Try to remove the NSTimer to see if it works well.