Setting Scroll View's zoomScale and hitting back button crashes program - swift

I was trying to make a program where you click an image and it segues to show it on full screen, scaled so that there is no whitespace around (CS 193P assignment 4 task 7 for anyone who knows it). View controllers are embedded in navigation controller. It turns out that the program crashes with EXC_BAD_ACCESS (code = EXC_I386_GPFLT) when I hit the back button in the navigation bar.
I found out that I can prevent this crash by scrolling to the top of the image and then going back. Another way to completely prevent it is to comment out the line that sets scrollView's zoomScale. Here's the code I used for loading and handling the image:
import UIKit
class ImageViewController: UIViewController, UIScrollViewDelegate {
#IBOutlet weak var scrollView: UIScrollView! {
didSet {
scrollView.contentSize = imageView.frame.size
scrollView.delegate = self
scrollView.minimumZoomScale = 0.03
scrollView.maximumZoomScale = 5.0
}
}
func viewForZoomingInScrollView(scrollView: UIScrollView) -> UIView? {
return imageView
}
var imageURL: NSURL? {
didSet {
image = nil
if view.window != nil {
fetchImage()
}
}
}
private func fetchImage() {
if let url = imageURL {
let qos = Int(QOS_CLASS_USER_INITIATED.value)
dispatch_async(dispatch_get_global_queue(qos, 0)) {
let imageData = NSData(contentsOfURL: url)
dispatch_async(dispatch_get_main_queue()) {
if url == self.imageURL {
if imageData != nil {
self.image = UIImage(data: imageData!)
} else {
self.image = nil
}
}
}
}
}
}
private var imageView = UIImageView()
private var image: UIImage? {
get { return imageView.image }
set {
imageView.image = newValue
imageView.sizeToFit()
scrollView?.contentSize = imageView.frame.size
scrollViewDidScrollOrZoom = false
autoScale()
}
}
private var scrollViewDidScrollOrZoom = false
private func autoScale() {
if scrollViewDidScrollOrZoom {
return
}
if let sv = scrollView {
if image != nil {
sv.zoomScale = max(sv.bounds.size.width / image!.size.width, sv.bounds.size.height / image!.size.height)
sv.contentOffset = CGPoint(x: (imageView.frame.size.width - sv.frame.size.width) / 2, y: (imageView.frame.size.height - sv.frame.size.height) / 2)
scrollViewDidScrollOrZoom = false
}
}
}
func scrollViewDidEndZooming(scrollView: UIScrollView, withView view: UIView!, atScale scale: CGFloat) {
scrollViewDidScrollOrZoom = true
}
func scrollViewDidScroll(scrollView: UIScrollView) {
scrollViewDidScrollOrZoom = true
}
override func viewDidLoad() {
super.viewDidLoad()
scrollView.addSubview(imageView)
}
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
if image == nil {
fetchImage()
}
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
autoScale()
}
}
I tried to make a simple application which segues into ImageViewController from a View Controller that has only a button and is embedded in navigation controller, and the crash is still the same. Here's the code I used in the first view controller:
class FirstViewController: UIViewController {
let url = NSURL(string: "https://developer.apple.com/swift/images/swift-og.png")
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "Show Full Image" {
let ivc = segue.destinationViewController as! ImageViewController
ivc.imageURL = url
}
}
}
What causes this and am I doing something wrong here? I'm using Xcode 6.3 beta 3.

It seems that the problem is actually with the scrollView's delegate, as I was able to fix this with adding
override func viewWillDisappear(animated: Bool) {
super.viewWillDisappear(animated)
scrollView.delegate = nil
}

Related

Custom Image issue on Page control

i'm trying to set custom image on page control dots. I have tried a custom class for this ,
class CustomPageControl: UIPageControl {
#IBInspectable var currentPageImage: UIImage?
#IBInspectable var otherPagesImage: UIImage?
override var numberOfPages: Int {
didSet {
updateDots()
}
}
override var currentPage: Int {
didSet {
updateDots()
}
}
override func awakeFromNib() {
super.awakeFromNib()
pageIndicatorTintColor = .clear
currentPageIndicatorTintColor = .clear
clipsToBounds = false
}
private func updateDots() {
for (index, subview) in subviews.enumerated() {
let imageView: UIImageView
if let existingImageview = getImageView(forSubview: subview) {
imageView = existingImageview
} else {
imageView = UIImageView(image: otherPagesImage)
imageView.center = subview.center
subview.addSubview(imageView)
subview.clipsToBounds = false
}
imageView.image = currentPage == index ? currentPageImage : otherPagesImage
}
}
private func getImageView(forSubview view: UIView) -> UIImageView? {
if let imageView = view as? UIImageView {
return imageView
} else {
let view = view.subviews.first { (view) -> Bool in
return view is UIImageView
} as? UIImageView
return view
}
}
}
After running this code i got this result in my VC,
enter image description here
I want this result from this,
enter image description here

Set page control to the top of UIPageViewController

Currently i want to use UIPageViewController. But by default the page control is automatic place on the bottom of screen. so how can i achieve like the image below where the page control is on the top
i use container view and change the view controller to uipageviewcontroller
UPDATE
class ProfilePageViewController: UIPageViewController, UIPageViewControllerDelegate, UIPageViewControllerDataSource {
var pageControl = UIPageControl(frame: CGRect(x: 0,y: 0,width: UIScreen.main.bounds.width,height: 50))
lazy var VCArr: [UIViewController] = {
return [self.VCInstance(name: "button1"),
self.VCInstance(name: "button2"),
self.VCInstance(name: "button3")]
}()
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
for view in self.view.subviews {
if view is UIScrollView {
view.frame = UIScreen.main.bounds
} else if view is UIPageControl {
view.backgroundColor = UIColor.clear
}
}
}
private func VCInstance(name: String) -> UIViewController {
return UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: name)
}
override func viewDidLoad() {
super.viewDidLoad()
self.dataSource = self
self.delegate = self
pageControl.currentPageIndicatorTintColor = UIColor.green
pageControl.pageIndicatorTintColor = UIColor.gray
if let firstVC = VCArr.first {
setViewControllers([firstVC], direction: .forward, animated: true, completion: nil)
}
}
public func pageViewController(_ pageViewController: UIPageViewController, viewControllerBefore viewController: UIViewController) -> UIViewController? {
guard let viewControllerIndex = VCArr.index(of: viewController) else {
return nil
}
let previousIndex = viewControllerIndex - 1
guard previousIndex >= 0 else {
return VCArr.last
//uncomment atas comment bawa untuk loop
//return nil
}
guard VCArr.count > previousIndex else {
return nil
}
return VCArr[previousIndex]
}
public func pageViewController(_ pageViewController: UIPageViewController, viewControllerAfter viewController: UIViewController) -> UIViewController? {
guard let viewControllerIndex = VCArr.index(of: viewController) else {
return nil
}
let nextIndex = viewControllerIndex + 1
guard nextIndex < VCArr.count else {
return VCArr.first
//uncomment atas, comment bawah untuk loop
//return nil
}
guard VCArr.count > nextIndex else {
return nil
}
return VCArr[nextIndex]
}
public func presentationCount(for pageViewController: UIPageViewController) -> Int {
return VCArr.count
}
public func presentationIndex(for pageViewController: UIPageViewController) -> Int {
guard let firstViewController = viewControllers?.first,
let firstViewControllerIndex = VCArr.index(of: firstViewController) else {
return 0
}
return firstViewControllerIndex
}
}
i have already updated the latest code what i had already did, but the page control seem still at the bottom of page.
below is the image of my current page control
Don't use the default UIPageControl from appearance. Remove those block.
Create a new UIPageControl based on your view rect and position.
In frame of UIpagecontrol: set y = 0 means top or
UIScreen.main.bounds.maxY - offset
Here is the sample code
//1 : member variable
var pageControl = UIPageControl()
// Call it from viewDidLoad
func setupPageControl() {
// 2: set y = 0 means top or UIScreen.main.bounds.maxY - offset
pageControl = UIPageControl(frame: CGRect(x: 0,y: 0,width: UIScreen.main.bounds.width,height: 50))
self.pageControl.numberOfPages = pageCount
// self.pageControl.currentPage = 0
self.pageControl.tintColor = UIColor.lightGray
self.pageControl.pageIndicatorTintColor = UIColor.lightGray
self.pageControl.currentPageIndicatorTintColor = UIColor.black
pageControl.backgroundColor = UIColor.clear
self.view.addSubview(pageControl)
}
You need to update the current page (pageControl.currentPage = count) from viewControllerAfter () & viewControllerBefore

How to stop after view scroll in page controller at particular view

I integrated pageviewcontroller using storyboard. i have 4 view. how to stop after scroll at third view. how to disable after scoll?
This my code. how to stop after scroll in ThirdViewController.
import UIKit
class PageViewController: UIPageViewController, UIPageViewControllerDelegate, UIPageViewControllerDataSource {
var pageControl = UIPageControl()
// MARK: UIPageViewControllerDataSource
lazy var orderedViewControllers: [UIViewController] = {
return [self.newVc(viewController: "sbRed"),self.newVc(viewController: "sbBlue"),self.newVc(viewController: "sbOrange"),self.newVc(viewController: "sbGray")]
}()
override func viewDidLoad() {
super.viewDidLoad()
self.dataSource = self
self.delegate = self
// This sets up the first view that will show up on our page control
if let firstViewController = orderedViewControllers.first {
setViewControllers([firstViewController],
direction: .forward,
animated: true,
completion: nil)
}
configurePageControl()
// Do any additional setup after loading the view.
}
func configurePageControl() {
// The total number of pages that are available is based on how many available colors we have.
pageControl = UIPageControl(frame: CGRect(x: 0,y: UIScreen.main.bounds.maxY - 50,width: UIScreen.main.bounds.width,height: 50))
self.pageControl.numberOfPages = orderedViewControllers.count
self.pageControl.currentPage = 0
self.pageControl.tintColor = UIColor.black
self.pageControl.pageIndicatorTintColor = UIColor.white
self.pageControl.currentPageIndicatorTintColor = UIColor.black
self.view.addSubview(pageControl)
}
func newVc(viewController: String) -> UIViewController {
return UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: viewController)
}
// MARK: Delegate methords
func pageViewController(_ pageViewController: UIPageViewController, didFinishAnimating finished: Bool, previousViewControllers: [UIViewController], transitionCompleted completed: Bool) {
let pageContentViewController = pageViewController.viewControllers![0]
self.pageControl.currentPage = orderedViewControllers.index(of: pageContentViewController)!
}
// MARK: Data source functions.
func pageViewController(_ pageViewController: UIPageViewController, viewControllerBefore viewController: UIViewController) -> UIViewController? {
guard let viewControllerIndex = orderedViewControllers.index(of: viewController) else {
return nil
}
let previousIndex = viewControllerIndex - 1
// User is on the first view controller and swiped left to loop to
// the last view controller.
guard previousIndex >= 0 else {
return orderedViewControllers.last
// Uncommment the line below, remove the line above if you don't want the page control to loop.
// return nil
}
guard orderedViewControllers.count > previousIndex else {
return nil
}
return orderedViewControllers[previousIndex]
}
func pageViewController(_ pageViewController: UIPageViewController, viewControllerAfter viewController: UIViewController) -> UIViewController? {
guard let viewControllerIndex = orderedViewControllers.index(of: viewController) else {
return nil
}
let nextIndex = viewControllerIndex + 1
let orderedViewControllersCount = orderedViewControllers.count
// User is on the last view controller and swiped right to loop to
// the first view controller.
guard orderedViewControllersCount != nextIndex else {
return orderedViewControllers.first
// Uncommment the line below, remove the line above if you don't want the page control to loop.
// return nil
}
guard orderedViewControllersCount > nextIndex else {
return nil
}
return orderedViewControllers[nextIndex]
}
}
import UIKit
class ThirdViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
You need to use UiPagerViewDatasource methods :
a) func pageViewController(UIPageViewController, viewControllerBefore: UIViewController)
b) func pageViewController(UIPageViewController, viewControllerAfter: UIViewController)
Example:
func pageViewController(UIPageViewController, viewControllerBefore: UIViewController){
if viewController == controllerA {
return controllerB;
}
else if viewController == controllerB {
return controllerC;
}
else ifviewController == controllerC {
return nil;
}}
and
func pageViewController(UIPageViewController, viewControllerAfter: UIViewController){
if (viewController == controllerC) {
return controllerB;
}
else if(viewController == controllerB) {
return controllerA;
}
else {
return nil;
}}
Sets the view controllers to be displayed you need to call :
pager.setViewControllers([selectingViewController]?, direction: UIPageViewControllerNavigationDirectionForward, animated: false, completion:nil)

How to Enable scroll in PageViewController after disable delay in button highlighted in SWIFT 2.0

I just have trouble with button highlighted delay in UIPageViewController.
I can solve now by setting property "delaysContentTouches" = false to subview.
But now i can't scroll page anymore. i search forum and found "gestureRecognizers" which might help. but i don't know how to set.
could you help suggest me?
import UIKit
class PageViewController: UIPageViewController, UIPageViewControllerDataSource, UIPageViewControllerDelegate {
var pages = [UIViewController]()
override func viewDidLoad() {
super.viewDidLoad()
self.delegate = self
self.dataSource = self
//declare all pages
let page1: UIViewController! = storyboard?.instantiateViewControllerWithIdentifier("c01")
let page2: UIViewController! = storyboard?.instantiateViewControllerWithIdentifier("c02")
let page3: UIViewController! = storyboard?.instantiateViewControllerWithIdentifier("c03")
pages.append(page1)
pages.append(page2)
pages.append(page3)
setViewControllers([page1], direction: UIPageViewControllerNavigationDirection.Forward, animated: false, completion: nil)
}
override func viewDidAppear(animated: Bool) {
for view in self.view.subviews {
if view is UIScrollView {
(view as? UIScrollView)!.delaysContentTouches = false
}
}
}
func pageViewController(pageViewController: UIPageViewController, viewControllerBeforeViewController viewController: UIViewController) -> UIViewController? {
guard let viewControllerIndex = pages.indexOf(viewController) else {
return nil
}
let previousIndex = viewControllerIndex - 1
guard previousIndex >= 0 else {
return pages.last
}
guard pages.count > previousIndex else {
return nil
}
return pages[previousIndex]
}
func pageViewController(pageViewController: UIPageViewController, viewControllerAfterViewController viewController: UIViewController) -> UIViewController? {
guard let viewControllerIndex = pages.indexOf(viewController) else {
return nil
}
let nextIndex = viewControllerIndex + 1
let orderedViewControllersCount = pages.count
guard orderedViewControllersCount != nextIndex else {
return pages.first
}
guard orderedViewControllersCount > nextIndex else {
return nil
}
return pages[nextIndex]
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
I can do it now.
It is not possible for uipageviewcontroller.
i have to move to uiscrollviewcontroller
then subclass it and override function touchesShouldCancelInContentView for all my UIButton to be true.
now i can tap button without delay and can still hold my finger to scroll.

Why is this didSet property not printing any output in Swift?

I have correctly set up a UIPageViewController.
I would like to check the itemIndex of each views and display content accordingly. Here's what the code looks like:
var itemIndex: Int = 0 {
didSet {
if itemIndex == 1 {
println("Character is Lela!")
}
if itemIndex == 0 {
println("Character is John!")
}
}
}
What I tried
I run the app
Console Output: "Character is John!"
I swipe forward (to the right)
Console Output: "Character is Lela!"
I swipe back (to the left)
There is no output!
I swipe forward
There is no output! Again!
What is this due to? Here's the full code:
import UIKit
var currentIndex: Int = 0
var nextIndex: Int = 0
class ProView: UIViewController, UIPageViewControllerDataSource, UIPageViewControllerDelegate {
var pageViewController: UIPageViewController?
let characterImages = ["character1", "character2", "character1", "character2", "character1", "character2", "character1", "character2"]
override func viewDidLoad() {
super.viewDidLoad()
createPageViewController()
setupPageControl()
character = 1
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
// Forward, check if this IS NOT the last controller
func pageViewController(pageViewController: UIPageViewController,
viewControllerAfterViewController ProView: UIViewController) -> UIViewController? {
let itemController = ProView as PageItemController
// Check if there is another view
if itemController.itemIndex+1 < characterImages.count {
return getItemController(itemController.itemIndex+1)
}
return nil
}
// Check if this IS NOT the first controller
func pageViewController(pageViewController: UIPageViewController,
viewControllerBeforeViewController ProView: UIViewController) -> UIViewController? {
let itemController = ProView as PageItemController
if itemController.itemIndex < 0 {
return getItemController(itemController.itemIndex-1)
}
return nil
}
private func getItemController(itemIndex: Int) -> PageItemController? {
if itemIndex < characterImages.count {
let pageItemController = self.storyboard!.instantiateViewControllerWithIdentifier("ItemController") as PageItemController
pageItemController.itemIndex = itemIndex
pageItemController.imageName = characterImages[itemIndex]
return pageItemController
}
return nil
}
func createPageViewController() {
let pageController = self.storyboard!.instantiateViewControllerWithIdentifier("PageController") as UIPageViewController
pageController.dataSource = self
pageController.delegate = self
if characterImages.count > 0 {
let firstController = getItemController(0)!
let startingViewControllers: NSArray = [firstController]
pageController.setViewControllers(startingViewControllers, direction: UIPageViewControllerNavigationDirection.Forward, animated: false, completion: nil)
}
pageViewController = pageController
addChildViewController(pageViewController!)
self.view.addSubview(pageViewController!.view)
pageViewController?.didMoveToParentViewController(self)
}
func setupPageControl() {
let appearance = UIPageControl.appearance()
appearance.pageIndicatorTintColor = UIColor.grayColor()
appearance.currentPageIndicatorTintColor = UIColor.whiteColor()
}
func presentationCountForPageViewController(pageViewController: UIPageViewController) -> Int {
return characterImages.count
}
func presentationIndexForPageViewController(pageViewController: UIPageViewController) -> Int {
return 0
}
// BETA
func pageViewController(PageItemController: UIPageViewController,
didFinishAnimating finished: Bool,
previousViewControllers pageViewController: [AnyObject],
transitionCompleted completed: Bool)
{
if (!completed)
{
// You do nothing because whatever page you thought
// the book was on before the gesture started is still the correct page
return;
}
// This is where you would know the page number changed and handle it appropriately
}
}
class PageItemController: UIViewController {
#IBOutlet weak var imageCharacterChoose: UIImageView!
var itemIndex: Int = 0 {
didSet {
if itemIndex == 1 {
println("Character is Lela!")
}
if itemIndex == 0 {
println("Character is John!")
}
}}
var imageName: String = "" {
didSet {
if let imageView = imageCharacterChoose {imageCharacterChoose.image = UIImage(named: imageName)
}
}
}
override func viewDidLoad() {
super.viewDidLoad()
imageCharacterChoose!.image = UIImage(named: imageName)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
}
didSet and willSet will only get called when you change the value of the corresponding variable, so in your case you should somewhere in your code do itemIndex = 1, or some other value to trigger the functions.