How to keep UIButton floating AFTER scrollViewDidScroll(_ scrollView: UIScrollView) reached bottom? - ios

The following is the current implementation. The grey button is floating on the scroll view. Is there a way to make the button appear once the yellow view (end of scroll view) is reached. Then keep it floating on the screen at the very bottom.
I'm using the following code:
override func scrollViewDidScroll(scrollView: UIScrollView) {
if (scrollView.contentOffset.y >= (scrollView.contentSize.height - scrollView.frame.size.height)) {
//reached bottom - how to show button below yellow
// and keep it floating as shown above
}
}
Adding additional code of what I've tried so far:
#IBOutlet weak var btnScroll: UIButton!
var startingFrame : CGRect!
var endingFrame : CGRect!
func scrollViewDidScroll(_ scrollView: UIScrollView) {
if (scrollView.contentOffset.y >= (scrollView.contentSize.height - scrollView.frame.size.height)) && self.btnScroll.isHidden {
self.btnScroll.isHidden = false
self.btnScroll.frame = startingFrame // outside of screen somewhere in bottom
UIView.animate(withDuration: 1.0) {
self.btnScroll.frame = self.endingFrame // where it should be placed
}
}
}
func configureSizes() {
let screenSize = UIScreen.main.bounds
let screenWidth = screenSize.width
let screenHeight = screenSize.height
startingFrame = CGRect(x: 0, y: screenHeight+100, width: screenWidth, height: 100)
endingFrame = CGRect(x: 0, y: screenHeight-100, width: screenWidth, height: 100)
}
override func viewDidLoad() {
super.viewDidLoad()
configureSizes()
}

If I understand you right you want to put button on the position which shown on the gif
Try this code:
override func scrollViewDidScroll(scrollView: UIScrollView) {
if (scrollView.contentOffset.y >= (scrollView.contentSize.height - scrollView.frame.size.height)) && self.button.isHidden {
self.button.isHidden = false
self.button.frame = startingFrame // outside of screen somewhere in bottom
UIView.animate(withDuration: 1.0) {
self.button.frame = yourFrame // where it should be placed
}
}
}
UPDATE
add this code to hide your button before animation will show it
override func viewDidLoad() {
super.viewDidLoad()
self.button.isHidden = true
...
}

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

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]

ScrollView and little horizontal bar under menu items?

I have a horizontal bar view under my menu items and whenever I tap one of the menu items it appears under that item. I have named it movingView inside my code. Here is a screenshot:
And under that menu I have a UIScrollView. I have placed 5 views inside that UIScrollView and I am able to swipe between them. Now whenever I swipe, I want that horizontal bar view under my menu item follow my selection. For example when I tap pistol, it moves from rifle to pistol. I want that to happen when I swipe from first page to second inside the UIScrollView.
Please someone help me. It's been 3 days and I couldn't figure out how to do this.
MY CODE:
class TabViewController: UIViewController
{
#IBOutlet var scrollView: UIScrollView!
#IBOutlet var buttons: [UIButton]!
#IBOutlet var backgroundView: UIImageView!
var movingView = UIView()
let screenWidth = UIScreen.main.bounds.width
var rifleViewController: UIViewController!
var pistolViewController: UIViewController!
var shotgunViewController: UIViewController!
var smgsViewController: UIViewController!
var sniperViewController: UIViewController!
override func viewDidLoad()
{
super.viewDidLoad()
let main = UIStoryboard(name: "Main", bundle: nil)
rifleViewController = main.instantiateViewController(withIdentifier: "rifles")
sniperViewController = main.instantiateViewController(withIdentifier: "snipers")
smgsViewController = main.instantiateViewController(withIdentifier: "smgss")
shotgunViewController = main.instantiateViewController(withIdentifier: "shotguns")
pistolViewController = main.instantiateViewController(withIdentifier: "pistols")
self.scrollView.contentSize = CGSize(width: self.view.frame.width * 5, height: self.view.frame.height)
rifleViewController.view.frame = scrollView.bounds
scrollView.addSubview(rifleViewController.view)
pistolViewController.view.frame = scrollView.bounds
pistolViewController.view.frame.origin.x = scrollView.frame.size.width
scrollView.addSubview(pistolViewController.view)
shotgunViewController.view.frame = scrollView.bounds
shotgunViewController.view.frame.origin.x = scrollView.frame.size.width * 2
scrollView.addSubview(shotgunViewController.view)
smgsViewController.view.frame = scrollView.bounds
smgsViewController.view.frame.origin.x = scrollView.frame.size.width * 3
scrollView.addSubview(smgsViewController.view)
sniperViewController.view.frame = scrollView.bounds
sniperViewController.view.frame.origin.x = scrollView.frame.size.width * 4
scrollView.addSubview(sniperViewController.view)
movingView = UIView(frame: CGRect(x: 0, y: backgroundView.frame.maxY - 5, width: screenWidth / 5, height: 10))
movingView.backgroundColor = UIColor.white
backgroundView.addSubview(movingView)
}
override func didReceiveMemoryWarning()
{
super.didReceiveMemoryWarning()
}
#IBAction func riflePressTab(_ sender: Any)
{
scrollView.setContentOffset(CGPoint(x: 0, y: 0) , animated: true)
}
#IBAction func pistolPressTab(_ sender: Any)
{
scrollView.setContentOffset(CGPoint(x: scrollView.frame.size.width, y: 0) , animated: true)
}
#IBAction func shotgunPressTab(_ sender: Any)
{
scrollView.setContentOffset(CGPoint(x: scrollView.frame.size.width * 2, y: 0) , animated: true)
}
#IBAction func smgsPressTab(_ sender: Any)
{
scrollView.setContentOffset(CGPoint(x: scrollView.frame.size.width * 3, y: 0) , animated: true)
}
#IBAction func sniperPressTab(_ sender: Any)
{
scrollView.setContentOffset(CGPoint(x: scrollView.frame.size.width * 4, y: 0) , animated: true)
}
#IBAction func didPressTab(_ sender: UIButton)
{
let newx = sender.frame.origin.x
UIView.animate(withDuration: 0.2)
{
self.movingView.frame.origin.x = newx
}
}
}
I have tried this but when i run my app, nothing happens when i scroll
func scrollViewDidScroll(_ scrollView: UIScrollView) {
//Assuming your views are of full screen width or just change the SCREEN_WIDTH to whatever width your views occupy
//This will give you which index you are on, i.e you are showing the first view or second or third
let newIndex = scrollView.contentOffset.x/UIScreen.main.bounds.size.width;
animateUnderBar(forIndex index:Int(newIndex))
}
func animateUnderBar(forIndex index:Int){
let newx = self.movingView.frame.origin.x + CGFloat(index) * self.movingView.bounds.size.width
UIView.animate(withDuration: 0.2)
{
self.movingView.frame.origin.x = newx
}
}
Make your class conform to the UIScrollViewDelegate and implement the following method-
func scrollViewDidScroll(_ scrollView: UIScrollView) {
//Assuming your views are of full screen width or just change the SCREEN_WIDTH to whatever width your views occupy
//This will give you which index you are on, i.e you are showing the first view or second or third
let newIndex = scrollView.contentOffset.x/UIScreen.main.bounds.size.width;
animateUnderBar(forIndex :Int(newIndex))
}
func animateUnderBar(forIndex index:Int){
let newx = view.frame.origin.x + CGFloat(index) * view.bounds.size.width
UIView.animate(withDuration: 0.2)
{
self.movingView.frame.origin.x = newx
}
}
}
Alternatively
You should use a UICollectionView instead of a UIScrollView as it will automatically handle displaying of views inside of cells without you having to manually set contentOffSet. Moving under bar will also be easier as it will simply be a matter of using the indexPath.item property to move the under bar.

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)
}
}

swift-Can't center UIImageView in UIScrollView with AutoLayout

I have searched many articles about UIScrollView with an UIImageView in, but all without AutoLayout info. What I have done is: place a UIScrollView in UIViewController with constraints top 10, leading 10, bottom 50, trailing 10; place a UIImageView inside of the UIScrollView with constraints top 0, leading 0, bottom 0, trailing 0. What I want is simple: let my UIImageView fit my UIScrollView when app first loaded, and let my UIImageView centered horizontally and vertically. Now I can see the image and just fit the screen nicely but the image is just at the top of UIScrollView.(notes: in this circumstance, the width of my image after being zoomed to minimum zoomScale equals to the width of UIScrollView, the height of my image after being zoom to minimum zoomScale less than the width of UIScrollView. )
I really can't move my UIImageView to center of UIScrollView vertically and can not find where I made an error. Thanks a lot.
import UIKit
class ViewController: UIViewController, UIScrollViewDelegate {
#IBOutlet weak var scrollView: UIScrollView!
#IBOutlet weak var imageView: UIImageView!
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.
}
func centerScrollViewContents() {
let boundsSize = scrollView.bounds.size
var contentsFrame = imageView.frame
if contentsFrame.size.width < boundsSize.width {
contentsFrame.origin.x = (boundsSize.width - contentsFrame.size.width) / 2.0
} else {
contentsFrame.origin.x = 0.0
}
if contentsFrame.size.height < boundsSize.height {
contentsFrame.origin.y = (boundsSize.height - contentsFrame.size.height) / 2.0
} else {
contentsFrame.origin.y = 0.0
}
}
override func viewDidLayoutSubviews() {
let myImage = UIImage(named: "7.jpg")
imageView.image = myImage
let weightScale = scrollView.bounds.size.width / myImage!.size.width
let heightScale = scrollView.bounds.size.height / myImage!.size.height
let minScale = min(weightScale, heightScale)
let imagew = myImage!.size.width * minScale
let imageH = myImage!.size.height * minScale
let imageRect = CGRectMake(0, 0, imagew, imageH)
imageView.frame = imageRect
scrollView.contentSize = myImage!.size
scrollView.maximumZoomScale = 1.0
scrollView.minimumZoomScale = minScale
scrollView.zoomScale = minScale
imageView.frame = imageRect
centerScrollViewContents()
}
func viewForZoomingInScrollView(scrollView: UIScrollView) -> UIView? {
return imageView
}
}
Piggy backing on pic-o-matic's answer. I had a case where I needed to center the image only if it was smaller than the scrollview frame. Using insets made for a straightforward solution.
if imageView.frame.height <= scrollView.frame.height {
let shiftHeight = scrollView.frame.height/2.0 - scrollView.contentSize.height/2.0
scrollView.contentInset.top = shiftHeight
}
if imageView.frame.width <= scrollView.frame.width {
let shiftWidth = scrollView.frame.width/2.0 - scrollView.contentSize.width/2.0
scrollView.contentInset.left = shiftWidth
}
I experienced the same problem. I found a perfect solution for me, check out this github project: https://github.com/evgenyneu/ios-imagescroll-swift
How it essentially works is as follows: it adjusts your constraints as to center the image if it is zoomed out far enough and to enable it to zoom and scroll after the width or height exceeds the respective width or height of your container.
Found it thanks to this thread: Zooming UIImageView inside UIScrollView with autolayout (obj-c and swift solution available)!
// ViewController.swift
// image-scroll-swift
//
// Created by Evgenii Neumerzhitckii on 4/10/2014.
// Copyright (c) 2014 Evgenii Neumerzhitckii. All rights reserved.
//
import UIKit
let imageScrollLargeImageName = "wallabi.jpg"
class ViewController: UIViewController, UIScrollViewDelegate {
#IBOutlet weak var scrollView: UIScrollView!
#IBOutlet weak var imageView: UIImageView!
#IBOutlet weak var imageConstraintTop: NSLayoutConstraint!
#IBOutlet weak var imageConstraintRight: NSLayoutConstraint!
#IBOutlet weak var imageConstraintLeft: NSLayoutConstraint!
#IBOutlet weak var imageConstraintBottom: NSLayoutConstraint!
var lastZoomScale: CGFloat = -1
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
imageView.image = UIImage(named: imageScrollLargeImageName)
scrollView.delegate = self
updateZoom()
}
// Update zoom scale and constraints
// It will also animate because willAnimateRotationToInterfaceOrientation
// is called from within an animation block
//
// DEPRECATION NOTICE: This method is said to be deprecated in iOS 8.0. But it still works.
override func willAnimateRotationToInterfaceOrientation(
toInterfaceOrientation: UIInterfaceOrientation, duration: NSTimeInterval) {
super.willAnimateRotationToInterfaceOrientation(toInterfaceOrientation, duration: duration)
updateZoom()
}
func updateConstraints() {
if let image = imageView.image {
let imageWidth = image.size.width
let imageHeight = image.size.height
let viewWidth = view.bounds.size.width
let viewHeight = view.bounds.size.height
// center image if it is smaller than screen
var hPadding = (viewWidth - scrollView.zoomScale * imageWidth) / 2
if hPadding < 0 { hPadding = 0 }
var vPadding = (viewHeight - scrollView.zoomScale * imageHeight) / 2
if vPadding < 0 { vPadding = 0 }
imageConstraintLeft.constant = hPadding
imageConstraintRight.constant = hPadding
imageConstraintTop.constant = vPadding
imageConstraintBottom.constant = vPadding
// Makes zoom out animation smooth and starting from the right point not from (0, 0)
view.layoutIfNeeded()
}
}
// Zoom to show as much image as possible unless image is smaller than screen
private func updateZoom() {
if let image = imageView.image {
var minZoom = min(view.bounds.size.width / image.size.width,
view.bounds.size.height / image.size.height)
if minZoom > 1 { minZoom = 1 }
scrollView.minimumZoomScale = minZoom
// Force scrollViewDidZoom fire if zoom did not change
if minZoom == lastZoomScale { minZoom += 0.000001 }
scrollView.zoomScale = minZoom
lastZoomScale = minZoom
}
}
// UIScrollViewDelegate
// -----------------------
func scrollViewDidZoom(scrollView: UIScrollView) {
updateConstraints()
}
func viewForZoomingInScrollView(scrollView: UIScrollView) -> UIView? {
return imageView
}
}
Make sure your storyboard contains a viewcontroller. In the 'View' place a 'Scroll View' and in that 'Scroll View' place an 'Image View'.
Establish top/bottom/leading/trailing constraints of 0 (or whatever value you prefer) between the 'Scroll View' and the 'View'. Then establish the same constraints between the image and the scroll view and connect them to the #IBOutlets.
This should work fine as the code changes the constraints as you zoom.
I had the same "issue".. without auto layout though. I came up with this solution(using insets to center the image).
The automaticallyAdjustsScrollViewInsets property must be set to false.
override func viewDidLoad() {
super.viewDidLoad()
//create scrollview
self.automaticallyAdjustsScrollViewInsets = false
myScrollview = MyScrollview(frame: self.view.frame)
myScrollview.alwaysBounceHorizontal = true
myScrollview.alwaysBounceVertical = true
myScrollview.delegate = self
//....
}
override func viewWillLayoutSubviews() {
//setup frame portrait and landscape
myScrollview.frame = self.view.frame
myScrollview.zoomScale = self.view.frame.width / photo.size.width
myScrollview.minimumZoomScale = self.view.bounds.width / photo.size.width
// calculate inset to center vertically
let shift = myScrollview.frame.height/2.0 - myScrollview.contentSize.height/2.0
myScrollview.contentInset.top = shift
}
override func viewDidLayoutSubviews() {
}
func scrollViewDidZoom(scrollView: UIScrollView) {
let shift = myScrollview.frame.height/2.0 - myScrollview.contentSize.height/2.0
myScrollview.contentInset.top = shift
}
I used a subclass of UIScrollView, "MyScrollview" to "see" whats going on, i think this is useful to learn about scrollview, bounds frames and content size:
//
// MyScrollview.swift
// CampingDeHooiberg
//
// Created by Andre Lam on 09-03-15.
// Copyright (c) 2015 Andre Lam. All rights reserved.
//
import UIKit
class MyScrollview: UIScrollView {
override var bounds: CGRect { willSet { println("bounds set: \(newValue) contentSize:\(self.contentSize) frame:\(self.frame)")}}
override var contentSize: CGSize { willSet { println("bounds:\(self.bounds) set contentsize:\(newValue) frame: \(self.frame)")}}
override var frame: CGRect { willSet { println("bounds: \(self.bounds) contentSize:\(self.contentSize) frame set:\(newValue)")}}
override init(frame: CGRect) {
super.init(frame: frame)
}
required init(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}

Resources