How do I make a UIView slide up with a touch of a button from its original position and them bring it back down with a touch of a button? Using Swift and Xcode 6.
I have currently tried this:
#IBOutlet weak var DynView: UIView!
#IBAction func btnUp(sender: AnyObject) {
}
You have to implement an animation changing the DynView position on click. Here's an example:
#IBAction func btnUp(sender: AnyObject) {
let xPosition = DynView.frame.origin.x
let yPosition = DynView.frame.origin.y - 20 // Slide Up - 20px
let width = DynView.frame.size.width
let height = DynView.frame.size.height
UIView.animateWithDuration(1.0, animations: {
dynView.frame = CGRect(x: xPosition, y: yPosition, width: width, height: height)
})
}
Hi create this extends if you want. For Swift
Create File Extends.Swift and add this code
/**
Extension UIView
by DaRk-_-D0G
*/
extension UIView {
/**
Set x Position
:param: x CGFloat
by DaRk-_-D0G
*/
func setX(#x:CGFloat) {
var frame:CGRect = self.frame
frame.origin.x = x
self.frame = frame
}
/**
Set y Position
:param: y CGFloat
by DaRk-_-D0G
*/
func setY(#y:CGFloat) {
var frame:CGRect = self.frame
frame.origin.y = y
self.frame = frame
}
/**
Set Width
:param: width CGFloat
by DaRk-_-D0G
*/
func setWidth(#width:CGFloat) {
var frame:CGRect = self.frame
frame.size.width = width
self.frame = frame
}
/**
Set Height
:param: height CGFloat
by DaRk-_-D0G
*/
func setHeight(#height:CGFloat) {
var frame:CGRect = self.frame
frame.size.height = height
self.frame = frame
}
}
For Use (inherits Of UIView)
inheritsOfUIView.setX(x: 100)
button.setX(x: 100)
view.setY(y: 100)
I kinda combined the two most voted answers into one and updated to Swift 3. So basically created an extension that animates a view moving to a different position:
extension UIView {
func slideX(x:CGFloat) {
let yPosition = self.frame.origin.y
let height = self.frame.height
let width = self.frame.width
UIView.animate(withDuration: 1.0, animations: {
self.frame = CGRect(x: x, y: yPosition, width: width, height: height)
})
}
}
// MARK: - Properties
var bottomViewHeight: CGFloat = 200
var isViewHide = false
private let bottomView: UIView = {
let view = UIView()
view.backgroundColor = .red
view.translatesAutoresizingMaskIntoConstraints = false
return view
}()
private let showHideButton: UIButton = {
let button = UIButton()
button.setTitle("Show / Hide", for: .normal)
button.setTitleColor(.black, for: .normal)
button.translatesAutoresizingMaskIntoConstraints = false
button.addTarget(self, action: #selector(showHideButtonTapped(_:)), for: .touchUpInside)
return button
}()
// MARK: - Lifecycle
override func loadView() {
super.loadView()
view.addSubview(bottomView)
NSLayoutConstraint.activate([
bottomView.heightAnchor.constraint(equalToConstant: bottomViewHeight),
bottomView.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor),
bottomView.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor),
bottomView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor)
])
view.addSubview(showHideButton)
NSLayoutConstraint.activate([
showHideButton.widthAnchor.constraint(equalToConstant: 200),
showHideButton.heightAnchor.constraint(equalToConstant: 50),
showHideButton.centerXAnchor.constraint(equalTo: view.safeAreaLayoutGuide.centerXAnchor),
showHideButton.centerYAnchor.constraint(equalTo: view.safeAreaLayoutGuide.centerYAnchor)
])
}
override func viewDidLoad() {
super.viewDidLoad()
showHideView(isShow: isViewHide)
}
// MARK: - Selectors
#objc func showHideButtonTapped(_ sender: UIButton) {
print("👆 HIDE / SHOW BUTTON")
showHideView(isShow: isViewHide)
}
// MARK: - Functions
private func showHideView(isShow: Bool) {
if isShow {
UIView.animate(withDuration: 0.4) {
self.bottomView.transform = CGAffineTransform(translationX: 0, y: self.bottomViewHeight)
}
} else {
UIView.animate(withDuration: 0.4) {
self.bottomView.transform = CGAffineTransform(translationX: 0, y: 0)
}
}
isViewHide = !isViewHide
}
Related
I would like to create the stepper menu in IOS using swift, But I'm facing some issues. Here are the issues.
1) Portrait and landscape stepper menu is not propper.
2) How to set default step position with the method below method, It's working when button clicked. But I want to set when menu loads the first time.
self.stepView.setSelectedPosition(index: 2)
3) If it reached the position last, I would like to change the color for complete path parentPathRect.
4) Progress animation CABasicAnimation is not like the progress bar, I want to show the animation.
5) It should not remove the selected position color when changing the orientation.
As per my organization rules should not use third-party frameworks.
Can anyone help me with the solution? Or is there any alternative solution for this?
Here is my code:
import UIKit
class ViewController: UIViewController, StepMenuDelegate {
#IBOutlet weak var stepView: StepView!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
self.stepView.delegate = self;
self.stepView.titles = ["1", "2", "3"]
self.stepView.lineWidth = 8
self.stepView.offSet = 8
self.stepView.setSelectedPosition(index: 2)
}
func didSelectItem(atIndex index: NSInteger) {
print(index)
}
}
protocol StepMenuDelegate {
func didSelectItem(atIndex index: NSInteger)
}
class StepView: UIView {
var delegate : StepMenuDelegate!
var titles: [String] = [] {
didSet(values) {
setup()
setupItems()
}
}
var lineWidth: CGFloat = 8 {
didSet(values) {
setup()
}
}
var offSet: CGFloat = 8 {
didSet(values) {
self.itemOffset = offSet * 4
setup()
}
}
private var selectedIndex : NSInteger!
private var itemOffset : CGFloat = 8 {
didSet (value) {
setup()
setupItems()
}
}
private var path : UIBezierPath!
var selectedLayer : CAShapeLayer!
private var parentPathRect : CGRect!
override func awakeFromNib() {
super.awakeFromNib()
}
override func layoutSubviews() {
self.setup()
setupItems()
}
func setup() {
self.removeAllButtonsAndLayes()
let layer = CAShapeLayer()
self.parentPathRect = CGRect(origin: CGPoint(x: offSet, y: self.bounds.midY - (self.lineWidth/2) ), size: CGSize(width: self.bounds.width - (offSet * 2), height: lineWidth))
path = UIBezierPath(roundedRect: self.parentPathRect, cornerRadius: 2)
layer.path = path.cgPath
layer.fillColor = UIColor.orange.cgColor
layer.lineCap = .butt
layer.shadowColor = UIColor.darkGray.cgColor
layer.shadowOffset = CGSize(width: 1, height: 2)
layer.shadowOpacity = 0.1
layer.shadowRadius = 2
self.layer.addSublayer(layer)
}
func setupItems() {
removeAllButtonsAndLayes()
let itemRect = CGRect(x: self.itemOffset, y: 0, width: 34, height: 34)
let totalWidth = self.bounds.width
let itemWidth = totalWidth / CGFloat(self.titles.count);
for i in 0..<self.titles.count {
let button = UIButton()
var xPos: CGFloat = itemOffset
self.addSubview(button)
xPos += (CGFloat(i) * itemWidth);
xPos += itemOffset/3
button.translatesAutoresizingMaskIntoConstraints = false
button.leftAnchor.constraint(equalTo: self.leftAnchor, constant: xPos).isActive = true
button.centerYAnchor.constraint(equalTo: self.centerYAnchor, constant: 0).isActive = true
button.heightAnchor.constraint(equalToConstant: itemRect.height).isActive = true
button.widthAnchor.constraint(equalToConstant: itemRect.width).isActive = true
button.backgroundColor = UIColor.red
button.layer.zPosition = 1
button.layer.cornerRadius = itemRect.height/2
let name : String = self.titles[i]
button.tag = i
button.setTitle(name, for: .normal)
button.addTarget(self, action: #selector(selectedItemEvent(sender:)), for: .touchUpInside)
if self.selectedIndex != nil {
if button.tag == self.selectedIndex {
selectedItemEvent(sender: button)
}
}
}
}
#objc func selectedItemEvent(sender:UIButton) {
if self.selectedLayer != nil {
selectedLayer.removeFromSuperlayer()
}
self.delegate.didSelectItem(atIndex: sender.tag)
let fromRect = self.parentPathRect.origin
self.selectedLayer = CAShapeLayer()
let rect = CGRect(origin: fromRect, size: CGSize(width:sender.frame.origin.x - 4, height: 8))
let path = UIBezierPath(roundedRect: rect, cornerRadius: 4)
self.selectedLayer.path = path.cgPath
self.selectedLayer.lineCap = .round
self.selectedLayer.fillColor = UIColor.orange.cgColor
let animation = CABasicAnimation(keyPath: "fillColor")
animation.toValue = UIColor.blue.cgColor
animation.duration = 0.2
animation.fillMode = .forwards
animation.isRemovedOnCompletion = false
self.selectedLayer.add(animation, forKey: "fillColor")
self.layer.addSublayer(self.selectedLayer)
}
func removeAllButtonsAndLayes() {
for button in self.subviews {
if button is UIButton {
button.removeFromSuperview()
}
}
}
func setSelectedPosition(index:NSInteger) {
self.selectedIndex = index
}
}
Here I found One way to achieve the solution.!!
https://gist.github.com/DamodarGit/7f0f484708f60c996772ae28e5e1c615
Welcome to suggestions or code changes.!!
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'm using Swiftpages. When app is opened it looks like first picture.
But app goes to background and opened different app on device, after open again my app it looks like second picture.
I updated to Swift 3, but I can't figure out the issue, I write about it on Github but no reply from them.
public class SwiftPages: UIView {
private lazy var token = 0
var containerVieww: UIView!
private var scrollView: UIScrollView!
private var topBar: UIView!
var animatedBar: UIView!
var viewControllerIDs = [String]()
private var buttonTitles = [String]()
private var buttonImages = [UIImage]()
private var pageViews = [UIViewController?]()
private var currentPage: Int = 0
// Container view position variables
private var xOrigin: CGFloat = 0
private var yOrigin: CGFloat = 64
private var distanceToBottom: CGFloat = 0
// Color variables
private var animatedBarColor = UIColor(red: 28/255, green: 95/255, blue: 185/255, alpha: 1)
private var topBarBackground = UIColor.white
private var buttonsTextColor = UIColor.gray
private var containerViewBackground = UIColor.white
// Item size variables
private var topBarHeight: CGFloat = 52
private var animatedBarHeight: CGFloat = 3
// Bar item variables
private var aeroEffectInTopBar = false //This gives the top bap a blurred effect, also overlayes the it over the VC's
private var buttonsWithImages = false
var barShadow = true
private var shadowView : UIView!
private var shadowViewGradient = CAGradientLayer()
private var buttonsTextFontAndSize = UIFont(name: "HelveticaNeue-Light", size: 20)!
private var blurView : UIVisualEffectView!
private var barButtons = [UIButton?]()
// MARK: - Positions Of The Container View API -
public func setOriginX (origin : CGFloat) { xOrigin = origin }
public func setOriginY (origin : CGFloat) { yOrigin = origin }
public func setDistanceToBottom (distance : CGFloat) { distanceToBottom = distance }
// MARK: - API's -
public func setAnimatedBarColor (color : UIColor) { animatedBarColor = color }
public func setTopBarBackground (color : UIColor) { topBarBackground = color }
public func setButtonsTextColor (color : UIColor) { buttonsTextColor = color }
public func setContainerViewBackground (color : UIColor) { containerViewBackground = color }
public func setTopBarHeight (pointSize : CGFloat) { topBarHeight = pointSize}
public func setAnimatedBarHeight (pointSize : CGFloat) { animatedBarHeight = pointSize}
public func setButtonsTextFontAndSize (fontAndSize : UIFont) { buttonsTextFontAndSize = fontAndSize}
public func enableAeroEffectInTopBar (boolValue : Bool) { aeroEffectInTopBar = boolValue}
public func enableButtonsWithImages (boolValue : Bool) { buttonsWithImages = boolValue}
public func enableBarShadow (boolValue : Bool) { barShadow = boolValue}
override public func draw(_ rect: CGRect) {
DispatchQueue.main.async {
let pagesContainerHeight = self.frame.height - self.yOrigin - self.distanceToBottom
let pagesContainerWidth = self.frame.width
// Set the notifications for an orientation change & BG mode
let defaultNotificationCenter = NotificationCenter.default
defaultNotificationCenter.addObserver(self, selector: #selector(SwiftPages.applicationWillEnterBackground), name: NSNotification.Name.UIApplicationWillResignActive, object: nil)
defaultNotificationCenter.addObserver(self, selector: #selector(SwiftPages.orientationWillChange), name: NSNotification.Name.UIApplicationWillChangeStatusBarOrientation, object: nil)
defaultNotificationCenter.addObserver(self, selector: #selector(SwiftPages.orientationDidChange), name: NSNotification.Name.UIDeviceOrientationDidChange, object: nil)
defaultNotificationCenter.addObserver(self, selector: #selector(SwiftPages.applicationWillEnterForeground), name: NSNotification.Name.UIApplicationDidBecomeActive, object: nil)
// Set the containerView, every item is constructed relative to this view
self.containerVieww = UIView(frame: CGRect(x: self.xOrigin, y: self.yOrigin, width: pagesContainerWidth, height: pagesContainerHeight))
self.containerVieww.backgroundColor = self.containerViewBackground
self.containerVieww.translatesAutoresizingMaskIntoConstraints = false
self.addSubview(self.containerVieww)
//Add the constraints to the containerView.
if #available(iOS 9.0, *) {
let horizontalConstraint = self.containerVieww.centerXAnchor.constraint(equalTo: self.centerXAnchor)
let verticalConstraint = self.containerVieww.centerYAnchor.constraint(equalTo: self.centerYAnchor)
let widthConstraint = self.containerVieww.widthAnchor.constraint(equalTo: self.widthAnchor)
let heightConstraint = self.containerVieww.heightAnchor.constraint(equalTo: self.heightAnchor)
NSLayoutConstraint.activate([horizontalConstraint, verticalConstraint, widthConstraint, heightConstraint])
}
// Set the scrollview
if self.aeroEffectInTopBar {
self.scrollView = UIScrollView(frame: CGRect(x: 0, y: 0, width: self.containerVieww.frame.size.width, height: self.containerVieww.frame.size.height))
} else {
self.scrollView = UIScrollView(frame: CGRect(x: 0, y: self.topBarHeight, width: self.containerVieww.frame.size.width, height: self.containerVieww.frame.size.height - self.topBarHeight))
}
self.scrollView.isPagingEnabled = true
self.scrollView.showsHorizontalScrollIndicator = false
self.scrollView.showsVerticalScrollIndicator = false
self.scrollView.delegate = self
self.scrollView.backgroundColor = UIColor.clear
self.scrollView.contentOffset = CGPoint(x: 0, y: 0)
self.scrollView.translatesAutoresizingMaskIntoConstraints = false
self.scrollView.isScrollEnabled = false
self.containerVieww.addSubview(self.scrollView)
// Add the constraints to the scrollview.
if #available(iOS 9.0, *) {
let leadingConstraint = self.scrollView.leadingAnchor.constraint(equalTo: self.containerVieww.leadingAnchor)
let trailingConstraint = self.scrollView.trailingAnchor.constraint(equalTo: self.containerVieww.trailingAnchor)
let topConstraint = self.scrollView.topAnchor.constraint(equalTo: self.containerVieww.topAnchor)
let bottomConstraint = self.scrollView.bottomAnchor.constraint(equalTo: self.containerVieww.bottomAnchor)
NSLayoutConstraint.activate([leadingConstraint, trailingConstraint, topConstraint, bottomConstraint])
}
// Set the top bar
self.topBar = UIView(frame: CGRect(x: 0, y: 0, width: self.containerVieww.frame.size.width, height: self.topBarHeight))
self.topBar.backgroundColor = self.topBarBackground
if self.aeroEffectInTopBar {
// Create the blurred visual effect
// You can choose between ExtraLight, Light and Dark
self.topBar.backgroundColor = UIColor.clear
let blurEffect: UIBlurEffect = UIBlurEffect(style: .light)
self.blurView = UIVisualEffectView(effect: blurEffect)
self.blurView.frame = self.topBar.bounds
self.blurView.translatesAutoresizingMaskIntoConstraints = false
self.topBar.addSubview(self.blurView)
}
self.topBar.translatesAutoresizingMaskIntoConstraints = false
self.containerVieww.addSubview(self.topBar)
// Set the top bar buttons
// Check to see if the top bar will be created with images ot text
if self.buttonsWithImages {
var buttonsXPosition: CGFloat = 0
for (index, image) in self.buttonImages.enumerated() {
let frame = CGRect(x: buttonsXPosition, y: 0, width: self.containerVieww.frame.size.width / CGFloat(self.viewControllerIDs.count), height: self.topBarHeight)
let barButton = UIButton(frame: frame)
barButton.backgroundColor = UIColor.clear
barButton.imageView?.contentMode = .scaleAspectFit
barButton.setImage(image, for: .normal)
barButton.tag = index
barButton.addTarget(self, action: #selector(SwiftPages.barButtonAction), for: .touchUpInside)
self.topBar.addSubview(barButton)
self.barButtons.append(barButton)
buttonsXPosition += self.containerVieww.frame.size.width / CGFloat(self.viewControllerIDs.count)
}
} else {
var buttonsXPosition: CGFloat = 0
for (index, title) in self.buttonTitles.enumerated() {
let frame = CGRect(x: buttonsXPosition, y: 0, width: self.containerVieww.frame.size.width / CGFloat(self.viewControllerIDs.count), height: self.topBarHeight)
let barButton = UIButton(frame: frame)
barButton.backgroundColor = UIColor.clear
barButton.titleLabel!.font = self.buttonsTextFontAndSize
barButton.setTitle(title, for: .normal)
barButton.setTitleColor(self.buttonsTextColor, for: .normal)
barButton.tag = index
barButton.addTarget(self, action: #selector(SwiftPages.barButtonAction), for: .touchUpInside)
self.topBar.addSubview(barButton)
self.barButtons.append(barButton)
buttonsXPosition += self.containerVieww.frame.size.width / CGFloat(self.viewControllerIDs.count)
}
}
// Set up the animated UIView
self.animatedBar = UIView(frame: CGRect(x: 0, y: self.topBarHeight - self.animatedBarHeight + 1, width: (self.containerVieww.frame.size.width / CGFloat(self.viewControllerIDs.count)) * 0.8, height: self.animatedBarHeight))
self.animatedBar.center.x = self.containerVieww.frame.size.width / CGFloat(self.viewControllerIDs.count << 1)
self.animatedBar.backgroundColor = self.animatedBarColor
self.containerVieww.addSubview(self.animatedBar)
// Add the bar shadow (set to true or false with the barShadow var)
if self.barShadow {
self.shadowView = UIView(frame: CGRect(x: 0, y: self.topBarHeight, width: self.containerVieww.frame.size.width, height: 4))
self.shadowViewGradient.frame = self.shadowView.bounds
self.shadowViewGradient.colors = [UIColor(red: 150/255, green: 150/255, blue: 150/255, alpha: 0.28).cgColor, UIColor.clear.cgColor]
self.shadowView.layer.insertSublayer(self.shadowViewGradient, at: 0)
self.containerVieww.addSubview(self.shadowView)
}
let pageCount = self.viewControllerIDs.count
// Fill the array containing the VC instances with nil objects as placeholders
for _ in 0..<pageCount {
self.pageViews.append(nil)
}
// Defining the content size of the scrollview
let pagesScrollViewSize = self.scrollView.frame.size
self.scrollView.contentSize = CGSize(width: pagesScrollViewSize.width * CGFloat(pageCount), height: pagesScrollViewSize.height)
// Load the pages to show initially
self.loadVisiblePages()
// Do the initial alignment of the subViews
self.alignSubviews()
}
}
// MARK: - Initialization Functions -
public func initializeWithVCIDsArrayAndButtonTitlesArray (VCIDsArray: [String], buttonTitlesArray: [String]) {
// Important - Titles Array must Have The Same Number Of Items As The viewControllerIDs Array
if VCIDsArray.count == buttonTitlesArray.count {
viewControllerIDs = VCIDsArray
buttonTitles = buttonTitlesArray
buttonsWithImages = false
} else {
print("Initilization failed, the VC ID array count does not match the button titles array count.")
}
}
public func initializeWithVCIDsArrayAndButtonImagesArray (VCIDsArray: [String], buttonImagesArray: [UIImage]) {
// Important - Images Array must Have The Same Number Of Items As The viewControllerIDs Array
if VCIDsArray.count == buttonImagesArray.count {
viewControllerIDs = VCIDsArray
buttonImages = buttonImagesArray
buttonsWithImages = true
} else {
print("Initilization failed, the VC ID array count does not match the button images array count.")
}
}
public func loadPage(page: Int) {
// If it's outside the range of what you have to display, then do nothing
guard page >= 0 && page < viewControllerIDs.count else { return }
// Do nothing if the view is already loaded.
guard pageViews[page] == nil else { return }
print("Loading Page \(page)")
// The pageView instance is nil, create the page
var frame = scrollView.bounds
frame.origin.x = frame.size.width * CGFloat(page)
frame.origin.y = 0.0
// Look for the VC by its identifier in the storyboard and add it to the scrollview
let newPageView = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: viewControllerIDs[page])
newPageView.view.frame = frame
scrollView.addSubview(newPageView.view)
// Replace the nil in the pageViews array with the VC just created
pageViews[page] = newPageView
}
public func loadVisiblePages() {
// First, determine which page is currently visible
let pageWidth = scrollView.frame.size.width
let page = Int(floor((scrollView.contentOffset.x * 2.0 + pageWidth) / (pageWidth * 2.0)))
// Work out which pages you want to load
let firstPage = page - 1
let lastPage = page + 1
// Load pages in our range
for index in firstPage...lastPage {
loadPage(page: index)
}
}
public func barButtonAction(sender: UIButton?) {
let index = sender!.tag
let pagesScrollViewSize = scrollView.frame.size
scrollView.setContentOffset(CGPoint(x: pagesScrollViewSize.width * CGFloat(index), y: 0), animated: true)
currentPage = index
}
// MARK: - Orientation Handling Functions -
public func alignSubviews() {
let pageCount = viewControllerIDs.count
// Setup the new frames
scrollView.contentSize = CGSize(width: CGFloat(pageCount) * scrollView.bounds.size.width, height: scrollView.bounds.size.height)
topBar.frame = CGRect(x: 0, y: 0, width: containerVieww.frame.size.width, height: topBarHeight)
blurView?.frame = topBar.bounds
animatedBar.frame.size = CGSize(width: (containerVieww.frame.size.width / (CGFloat)(viewControllerIDs.count)) * 0.8, height: animatedBarHeight)
if barShadow {
shadowView.frame.size = CGSize(width: containerVieww.frame.size.width, height: 4)
shadowViewGradient.frame = shadowView.bounds
}
// Set the new frame of the scrollview contents
for (index, controller) in pageViews.enumerated() {
controller?.view.frame = CGRect(x: CGFloat(index) * scrollView.bounds.size.width, y: 0, width: scrollView.bounds.size.width, height: scrollView.bounds.size.height)
}
// Set the new frame for the top bar buttons
var buttonsXPosition: CGFloat = 0
for button in barButtons {
button?.frame = CGRect(x: buttonsXPosition, y: 0, width: containerVieww.frame.size.width / CGFloat(viewControllerIDs.count), height: topBarHeight)
buttonsXPosition += containerVieww.frame.size.width / CGFloat(viewControllerIDs.count)
}
}
func applicationWillEnterBackground() {
//Save the current page
currentPage = Int(scrollView.contentOffset.x / scrollView.bounds.size.width)
print("Haydar")
}
func orientationWillChange() {
//Save the current page
currentPage = Int(scrollView.contentOffset.x / scrollView.bounds.size.width)
}
func orientationDidChange() {
//Update the view
alignSubviews()
scrollView.contentOffset = CGPoint(x: CGFloat(currentPage) * scrollView.frame.size.width, y: 0)
}
func applicationWillEnterForeground() {
alignSubviews()
scrollView.contentOffset = CGPoint(x: CGFloat(currentPage) * scrollView.frame.size.width, y: 0)
initializeWithVCIDsArrayAndButtonTitlesArray(VCIDsArray: buttonTitles, buttonTitlesArray: buttonTitles)
print("ForegroundHound")
}
public func scrollViewDidEndDecelerating(scrollView: UIScrollView) {
let previousPage : NSInteger = currentPage
let pageWidth : CGFloat = scrollView.frame.size.width
let fractionalPage = scrollView.contentOffset.x / pageWidth
let page : NSInteger = Int(round(fractionalPage))
if (previousPage != page) {
currentPage = page;
}
}
deinit {
NotificationCenter.default.removeObserver(self)
print("deinittta")
}
}
extension SwiftPages: UIScrollViewDelegate {
public func scrollViewDidScroll(_ scrollView: UIScrollView) {
// Load the pages that are now on screen
loadVisiblePages()
// The calculations for the animated bar's movements
// The offset addition is based on the width of the animated bar (button width times 0.8)
let offsetAddition = (containerVieww.frame.size.width / CGFloat(viewControllerIDs.count)) * 0.1
animatedBar.frame = CGRect(x: (offsetAddition + (scrollView.contentOffset.x / CGFloat(viewControllerIDs.count))), y: animatedBar.frame.origin.y, width: animatedBar.frame.size.width, height: animatedBar.frame.size.height)
}
}
How can i implement UIScrollView when swiping snap to its subviews width. I need it to behave like pagingEnabled but i cant enable paging because i need my entire view can be swipe to scroll. if i do pagingEnable its impossible because scrollview bounds will be change to subviews width.
is there any possible way to do this.
please check the image for more details
import UIKit
class ViewController: UIViewController{
var navigationScroller: UIScrollView!
var contentScroller: UIScrollView!
var navContainer: UIView!
var contentContainer: UIView!
var selfWidth:CGFloat?
var navigationLabels = ["EVENTS", "MEMBERS", "SECTORS", "ORGANIZATIONS", "SEARCH", "EVENTS", "MEMBERS"]
private var buttonsTextFontAndSize: UIFont = UIFont(name: "HelveticaNeue-Light", size: 14)!
override func viewDidLoad() {
super.viewDidLoad()
selfWidth = self.view.frame.width
let frameWidth = self.view.frame.width
let frameHeight = self.view.frame.height
//let navscrollPosition = CGFloat( (frameWidth/2) - (75.0/2) )
navContainer = UIView(frame: CGRectMake(0.0, 75.0, frameWidth, 40.0))
navContainer.backgroundColor = UIColor(red:0, green:0.302, blue:0.522, alpha:1)
navigationScroller = UIScrollView(frame: CGRectMake(0.0, 0.0, frameWidth, 40.0))
navigationScroller.backgroundColor = UIColor.clearColor()
navigationScroller.pagingEnabled = false
navigationScroller.showsHorizontalScrollIndicator = false
navigationScroller.showsVerticalScrollIndicator = false
navigationScroller.clipsToBounds = false
navigationScroller.contentInset = UIEdgeInsetsZero
//navigationScroller.userInteractionEnabled = false
//navigationScroller.
addNavigationLabels(navigationScroller)
self.view.addSubview(navContainer)
navContainer.addSubview(navigationScroller)
navigationScroller.contentSize = CGSize(width: 150.0 * CGFloat(navigationLabels.count),height: 40.0)
navigationScroller.contentOffset = CGPoint(x: 170.0, y:0.0)
contentContainer = UIView(frame: CGRectMake(0.0, 115.0, frameWidth, frameHeight-115.0))
contentContainer.backgroundColor = UIColor.clearColor()
contentScroller = UIScrollView(frame: CGRectMake(0.0, 0.0, frameWidth, frameHeight-115.0))
contentScroller.backgroundColor = UIColor.clearColor()
contentScroller.pagingEnabled = true
contentScroller.showsHorizontalScrollIndicator = false
contentScroller.showsVerticalScrollIndicator = false
contentScroller.clipsToBounds = true
contentScroller.contentInset = UIEdgeInsetsZero
//contentScroller.addSubview(navContainer)
addContents(contentScroller)
self.view.addSubview(contentContainer)
contentContainer.addSubview(contentScroller)
contentScroller.contentSize = CGSize(width: frameWidth * CGFloat(navigationLabels.count),
height: frameHeight-115.0)
//contentScroller.delegate = self
navigationScroller.delegate = self
}
//MARK: -View Appeared function
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(true)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
// MARK: -Adding navigation labels fron navigation labels array
private func addNavigationLabels(navScrollView:UIScrollView){
var buttonsXPosition: CGFloat = 0
var buttonNumber = 0
for navLabel in navigationLabels {
var navButton: UIButton!
let red = CGFloat(buttonNumber) - 0.9
let frameWidth = self.view.frame.width
navButton = UIButton(frame: CGRectMake(buttonsXPosition, 0, frameWidth/3, 40.0))
navButton.titleLabel!.font = buttonsTextFontAndSize
navButton.contentHorizontalAlignment = .Center
navButton.backgroundColor = UIColor(red:red , green:0.114, blue:0.286, alpha:1)
navButton.setTitle(navLabel, forState: UIControlState.Normal)
navButton.addTarget(self, action: "buttonAction:", forControlEvents: UIControlEvents.TouchUpInside)
navScrollView.addSubview(navButton)
buttonsXPosition = frameWidth/3 + buttonsXPosition
buttonNumber++
}
}
func buttonAction(sender:UIButton!){
print("pressed")
}
// MARK: -Adding navigation labels fron navigation labels array
private func addContents(contentScroller:UIScrollView){
var buttonsXPosition: CGFloat = 0
var buttonNumber = 0
let frameWidth = self.view.frame.width
let frameHeight = self.view.frame.height
for navLabel in navigationLabels {
var navButton: UIButton!
navButton = UIButton(frame: CGRectMake(buttonsXPosition, 40.0, frameWidth, frameHeight-155))
navButton.titleLabel!.font = buttonsTextFontAndSize
navButton.contentHorizontalAlignment = .Center
navButton.backgroundColor = UIColor.darkGrayColor()
navButton.setTitle(navLabel, forState: UIControlState.Normal)
contentScroller.addSubview(navButton)
buttonsXPosition = frameWidth + buttonsXPosition
buttonNumber++
}
}
}
app view
You can implement UIScrollViewDelegate's method scrollViewWillEndDragging:withVelocity:targetContentOffset: and modify the offset at it will finish decelerating to match the width that you wish.
Something like this:
class ScrollSample: NSObject, UIScrollViewDelegate {
func scrollViewWillEndDragging(scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer<CGPoint>) {
let targetOffset = targetContentOffset.memory.x
// Round the offset to be a multiple of scrollview width
let roundedOffset = round(targetOffset / scrollView.frame.width) * scrollView.frame.width
targetContentOffset.memory = CGPoint(x: roundedOffset, y: 0)
}
}
I'm trying to implement Range Slider and I used custom control called NMRangeSlider.
But when I use it, the slider doesn't appear at all. Could it be also because it's all written in Objective-C?
This is how I've currently implemented it:
var rangeSlider = NMRangeSlider(frame: CGRectMake(16, 6, 275, 34))
rangeSlider.lowerValue = 0.54
rangeSlider.upperValue = 0.94
self.view.addSubview(rangeSlider)
To create a custom Range Slider I found a good solution here: range finder tutorial iOS 8 but I needed this in swift 3 for my project. I updated this for Swift 3 iOS 10 here:
in your main view controller add this to viewDidLayOut to show a range slider.
override func viewDidLayoutSubviews() {
let margin: CGFloat = 20.0
let width = view.bounds.width - 2.0 * margin
rangeSlider.frame = CGRect(x: margin, y: margin + topLayoutGuide.length + 170, width: width, height: 31.0)
}
create the helper function to print slider output below viewDidLayoutSubviews()
func rangeSliderValueChanged() { //rangeSlider: RangeSlider
print("Range slider value changed: \(rangeSlider.lowerValue) \(rangeSlider.upperValue) ")//(\(rangeSlider.lowerValue) \(rangeSlider.upperValue))
}
Create the file RangeSlider.swift and add this to it:
import UIKit
import QuartzCore
class RangeSlider: UIControl {
var minimumValue = 0.0
var maximumValue = 1.0
var lowerValue = 0.2
var upperValue = 0.8
let trackLayer = RangeSliderTrackLayer()//= CALayer() defined in RangeSliderTrackLayer.swift
let lowerThumbLayer = RangeSliderThumbLayer()//CALayer()
let upperThumbLayer = RangeSliderThumbLayer()//CALayer()
var previousLocation = CGPoint()
var trackTintColor = UIColor(white: 0.9, alpha: 1.0)
var trackHighlightTintColor = UIColor(red: 0.0, green: 0.45, blue: 0.94, alpha: 1.0)
var thumbTintColor = UIColor.white
var curvaceousness : CGFloat = 1.0
var thumbWidth: CGFloat {
return CGFloat(bounds.height)
}
override init(frame: CGRect) {
super.init(frame: frame)
trackLayer.rangeSlider = self
trackLayer.contentsScale = UIScreen.main.scale
layer.addSublayer(trackLayer)
lowerThumbLayer.rangeSlider = self
lowerThumbLayer.contentsScale = UIScreen.main.scale
layer.addSublayer(lowerThumbLayer)
upperThumbLayer.rangeSlider = self
upperThumbLayer.contentsScale = UIScreen.main.scale
layer.addSublayer(upperThumbLayer)
}
required init?(coder: NSCoder) {
super.init(coder: coder)
}
func updateLayerFrames() {
trackLayer.frame = bounds.insetBy(dx: 0.0, dy: bounds.height / 3)
trackLayer.setNeedsDisplay()
let lowerThumbCenter = CGFloat(positionForValue(value: lowerValue))
lowerThumbLayer.frame = CGRect(x: lowerThumbCenter - thumbWidth / 2.0, y: 0.0,
width: thumbWidth, height: thumbWidth)
lowerThumbLayer.setNeedsDisplay()
let upperThumbCenter = CGFloat(positionForValue(value: upperValue))
upperThumbLayer.frame = CGRect(x: upperThumbCenter - thumbWidth / 2.0, y: 0.0,
width: thumbWidth, height: thumbWidth)
upperThumbLayer.setNeedsDisplay()
}
func positionForValue(value: Double) -> Double {
return Double(bounds.width - thumbWidth) * (value - minimumValue) /
(maximumValue - minimumValue) + Double(thumbWidth / 2.0)
}
override var frame: CGRect {
didSet {
updateLayerFrames()
}
}
override func beginTracking(_ touch: UITouch, with event: UIEvent?) -> Bool {
previousLocation = touch.location(in: self)
// Hit test the thumb layers
if lowerThumbLayer.frame.contains(previousLocation) {
lowerThumbLayer.highlighted = true
} else if upperThumbLayer.frame.contains(previousLocation) {
upperThumbLayer.highlighted = true
}
return lowerThumbLayer.highlighted || upperThumbLayer.highlighted
}
func boundValue(value: Double, toLowerValue lowerValue: Double, upperValue: Double) -> Double {
return min(max(value, lowerValue), upperValue)
}
override func continueTracking(_ touch: UITouch, with event: UIEvent?) -> Bool {
let location = touch.location(in: self)
// 1. Determine by how much the user has dragged
let deltaLocation = Double(location.x - previousLocation.x)
let deltaValue = (maximumValue - minimumValue) * deltaLocation / Double(bounds.width - thumbWidth)
previousLocation = location
// 2. Update the values
if lowerThumbLayer.highlighted {
lowerValue += deltaValue
lowerValue = boundValue(value: lowerValue, toLowerValue: minimumValue, upperValue: upperValue)
} else if upperThumbLayer.highlighted {
upperValue += deltaValue
upperValue = boundValue(value: upperValue, toLowerValue: lowerValue, upperValue: maximumValue)
}
// 3. Update the UI
CATransaction.begin()
CATransaction.setDisableActions(true)
updateLayerFrames()
CATransaction.commit()
sendActions(for: .valueChanged)
return true
}
override func endTracking(_ touch: UITouch?, with event: UIEvent?) {
lowerThumbLayer.highlighted = false
upperThumbLayer.highlighted = false
}
}
Next add the thumb layer subclass file RangeSliderThumbLayer.swift and add this to it:
import UIKit
class RangeSliderThumbLayer: CALayer {
var highlighted = false
weak var rangeSlider: RangeSlider?
override func draw(in ctx: CGContext) {
if let slider = rangeSlider {
let thumbFrame = bounds.insetBy(dx: 2.0, dy: 2.0)
let cornerRadius = thumbFrame.height * slider.curvaceousness / 2.0
let thumbPath = UIBezierPath(roundedRect: thumbFrame, cornerRadius: cornerRadius)
// Fill - with a subtle shadow
let shadowColor = UIColor.gray
ctx.setShadow(offset: CGSize(width: 0.0, height: 1.0), blur: 1.0, color: shadowColor.cgColor)
ctx.setFillColor(slider.thumbTintColor.cgColor)
ctx.addPath(thumbPath.cgPath)
ctx.fillPath()
// Outline
ctx.setStrokeColor(shadowColor.cgColor)
ctx.setLineWidth(0.5)
ctx.addPath(thumbPath.cgPath)
ctx.strokePath()
if highlighted {
ctx.setFillColor(UIColor(white: 0.0, alpha: 0.1).cgColor)
ctx.addPath(thumbPath.cgPath)
ctx.fillPath()
}
}
}
}
Finally add the track layer subclass file RangeSliderTrackLayer.swift and add the following to it:
import Foundation
import UIKit
import QuartzCore
class RangeSliderTrackLayer: CALayer {
weak var rangeSlider: RangeSlider?
override func draw(in ctx: CGContext) {
if let slider = rangeSlider {
// Clip
let cornerRadius = bounds.height * slider.curvaceousness / 2.0
let path = UIBezierPath(roundedRect: bounds, cornerRadius: cornerRadius)
ctx.addPath(path.cgPath)
// Fill the track
ctx.setFillColor(slider.trackTintColor.cgColor)
ctx.addPath(path.cgPath)
ctx.fillPath()
// Fill the highlighted range
ctx.setFillColor(slider.trackHighlightTintColor.cgColor)
let lowerValuePosition = CGFloat(slider.positionForValue(value: slider.lowerValue))
let upperValuePosition = CGFloat(slider.positionForValue(value: slider.upperValue))
let rect = CGRect(x: lowerValuePosition, y: 0.0, width: upperValuePosition - lowerValuePosition, height: bounds.height)
ctx.fill(rect)
}
}
}
Build Run and Get:
UPDATE:
It did not show to me, because it was all white. So the solution, without using any other framework and sticking with this one - you need to set all the views for all the components and then it will display well:
I have tried to import it in Swift as I used it before in Objective-C code, but without any luck. If I set everything properly and add it either in viewDidLoad() or viewDidAppear(), nothing gets displayed. One thing is worth mentioning, though - when I enter View Debug Hierarchy, the slider actually is there on the canvas:
But it's simply not rendered with all the colors that I did set before adding in it to the view. For the record - this is the code I used:
override func viewDidAppear(animated: Bool) {
var rangeSlider = NMRangeSlider(frame: CGRectMake(50, 50, 275, 34))
rangeSlider.lowerValue = 0.54
rangeSlider.upperValue = 0.94
let range = 10.0
let oneStep = 1.0 / range
let minRange: Float = 0.05
rangeSlider.minimumRange = minRange
let bgImage = UIView(frame: rangeSlider.frame)
bgImage.backgroundColor = .greenColor()
rangeSlider.trackImage = bgImage.pb_takeSnapshot()
let trackView = UIView(frame: CGRectMake(0, 0, rangeSlider.frame.size.width, 29))
trackView.backgroundColor = .whiteColor()
trackView.opaque = false
trackView.alpha = 0.3
rangeSlider.trackImage = UIImage(named: "")
let lowerThumb = UIView(frame: CGRectMake(0, 0, 8, 29))
lowerThumb.backgroundColor = .whiteColor()
let lowerThumbHigh = UIView(frame: CGRectMake(0, 0, 8, 29))
lowerThumbHigh.backgroundColor = UIColor.blueColor()
rangeSlider.lowerHandleImageNormal = lowerThumb.pb_takeSnapshot()
rangeSlider.lowerHandleImageHighlighted = lowerThumbHigh.pb_takeSnapshot()
rangeSlider.upperHandleImageNormal = lowerThumb.pb_takeSnapshot()
rangeSlider.upperHandleImageHighlighted = lowerThumbHigh.pb_takeSnapshot()
self.view.addSubview(rangeSlider)
self.view.backgroundColor = .lightGrayColor()
}
Using the method for capturing the UIView as UIImage mentioned in this question:
extension UIView {
func pb_takeSnapshot() -> UIImage {
UIGraphicsBeginImageContextWithOptions(bounds.size, false, UIScreen.mainScreen().scale)
drawViewHierarchyInRect(self.bounds, afterScreenUpdates: true)
let image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return image
}
}
Other solution:
You can also try sgwilly/RangeSlider instead, it's written in Swift and therefore you won't even need a Bridging Header.
try this code :
override func viewDidLayoutSubviews() {
let margin: CGFloat = 20.0
let width = view.bounds.width - 2.0 * margin
rangeSlider.frame = CGRect(x: margin, y: margin + topLayoutGuide.length,
width: width, height: 31.0)
}
I implemented the range slider using :
https://github.com/Zengzhihui/RangeSlider
In the GZRangeSlider class, there is a method called :
private func setLabelText()
In that method, just put :
leftTextLayer.frame = CGRectMake(leftHandleLayer.frame.minX - 0.5 * (kTextWidth - leftHandleLayer.frame.width), leftHandleLayer.frame.minY - kTextHeight, kTextWidth, kTextHeight)
rightTextLayer.frame = CGRectMake(rightHandleLayer.frame.minX - 0.5 * (kTextWidth - leftHandleLayer.frame.width), leftTextLayer.frame.minY, kTextWidth, kTextHeight)
to animate the lower and upper labels..
This one is working well for me and its in swift.. just try it..