UIScrollView is eating up my swipe gestures Swift - swift

I have the below code that sets up a vertical scrollview My question is that the views within the view controllers do not respond accurately to swipe gestures. For example in this scrollview, bottomVc contains swipe gestures for left and right swipes. When this view controller is isolated in another project it works flawlessly, but the swipe gestures don't work when it is within the scrollview below. I was wondering how I could fix this issue so that my viewconttollers can handle swipe gestures while being in the scrollview. Also FYI, the code that controls the other view controllers are in separate files along with the swipe gestures.
The code:
import UIKit
class VerticalScrollViewController: UIViewController, SnapContainerViewControllerDelegate {
var topVc: UIViewController!
var middleVc: UIViewController!
var bottomVc: UIViewController!
var scrollView: UIScrollView!
class func verticalScrollVcWith(middleVc: UIViewController,
topVc: UIViewController?=nil,
bottomVc: UIViewController?=nil) -> VerticalScrollViewController {
let middleScrollVc = VerticalScrollViewController()
middleScrollVc.topVc = topVc
middleScrollVc.middleVc = middleVc
middleScrollVc.bottomVc = bottomVc
return middleScrollVc
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view:
setupScrollView()
}
func setupScrollView() {
scrollView = UIScrollView()
scrollView.isPagingEnabled = true
scrollView.showsVerticalScrollIndicator = false
scrollView.bounces = false
let view = (
x: self.view.bounds.origin.x,
y: self.view.bounds.origin.y,
width: self.view.bounds.width,
height: self.view.bounds.height
)
scrollView.frame = CGRect(x: view.x, y: view.y, width: view.width, height: view.height)
self.view.addSubview(scrollView)
let scrollWidth: CGFloat = view.width
var scrollHeight: CGFloat
if topVc != nil && bottomVc != nil {
scrollHeight = 3 * view.height
middleVc.view.frame = CGRect(x: 0, y: 0, width: view.width, height: view.height)
topVc.view.frame = CGRect(x: 0, y: view.height, width: view.width, height: view.height)
bottomVc.view.frame = CGRect(x: 0, y: 2 * view.height, width: view.width, height: view.height)
addChildViewController(topVc)
addChildViewController(middleVc)
addChildViewController(bottomVc)
scrollView.addSubview(topVc.view)
scrollView.addSubview(middleVc.view)
scrollView.addSubview(bottomVc.view)
topVc.didMove(toParentViewController: self)
middleVc.didMove(toParentViewController: self)
bottomVc.didMove(toParentViewController: self)
//scrollView.contentOffset.y = middleVc.view.frame.origin.y
scrollView.contentOffset.y = topVc.view.frame.origin.y
} else if topVc == nil {
scrollHeight = 2 * view.height
middleVc.view.frame = CGRect(x: 0, y: 0, width: view.width, height: view.height)
bottomVc.view.frame = CGRect(x: 0, y: view.height, width: view.width, height: view.height)
addChildViewController(middleVc)
addChildViewController(bottomVc)
scrollView.addSubview(middleVc.view)
scrollView.addSubview(bottomVc.view)
middleVc.didMove(toParentViewController: self)
bottomVc.didMove(toParentViewController: self)
scrollView.contentOffset.y = 0
} else if bottomVc == nil {
scrollHeight = 2 * view.height
topVc.view.frame = CGRect(x: 0, y: 0, width: view.width, height: view.height)
middleVc.view.frame = CGRect(x: 0, y: view.height, width: view.width, height: view.height)
addChildViewController(topVc)
addChildViewController(middleVc)
scrollView.addSubview(topVc.view)
scrollView.addSubview(middleVc.view)
topVc.didMove(toParentViewController: self)
middleVc.didMove(toParentViewController: self)
scrollView.contentOffset.y = middleVc.view.frame.origin.y
} else {
scrollHeight = view.height
middleVc.view.frame = CGRect(x: 0, y: 0, width: view.width, height: view.height)
addChildViewController(middleVc)
scrollView.addSubview(middleVc.view)
middleVc.didMove(toParentViewController: self)
}
scrollView.contentSize = CGSize(width: scrollWidth, height: scrollHeight)
scrollView.delaysContentTouches = false
}
func outerScrollViewShouldScroll() -> Bool {
if scrollView.contentOffset.y < middleVc.view.frame.origin.y || scrollView.contentOffset.y > middleVc.view.frame.origin.y {
return false
} else {
return true
}
}
}

Try adding the UIGestureRecognizerDelegate protocol to your class, then adding this method to return true :
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
return true
}
Should work, unless your controller setup is somehow preventing proper touch recognition..

Related

change position UiButton with Animation Swift

i have a button in ViewController and i changed button position when clicked on that .
the question it is how to can move button to new position when Clicked with animation ?
this is view Controller :
override func viewDidLoad() {
super.viewDidLoad()
let X = 50
let Y = 100
let WIDTH = 100
let HEIGHT = 100
movable_Button.frame = CGRect(x: X, y: Y, width: WIDTH , height: HEIGHT)
}
this is change position method When clicked :
#IBAction func movable_Button_DidTouch(_ sender: Any) {
movable_Button.frame = CGRect(x: X + 150, y: Y + 150, width: 100, height: 100)
}
Let's use UIView animate block
#IBAction func movableButtonDidTouch(_ sender: Any) {
// Set initial frame here
UIView.animate(withDuration: 0.35) {
// Set final frame here
}
}
Please notice to remove underline (_) in function name, it's not the convention of Swift, use camel case instead.
Update
class ViewController: UIViewController {
var positions = [CGPoint]()
let width = 100.0
let height = 50.0
var index = 0
lazy var button: UIButton = {
let theButton = UIButton(type: .system)
theButton.frame = CGRect(x: 50, y: 50, width: self.width, height: self.height)
theButton.backgroundColor = .red
return theButton
}()
override func viewDidLoad() {
super.viewDidLoad()
self.view.addSubview(self.button)
for index in 1...4 {
let point = CGPoint(x: 50 * index, y: 50 * index)
self.positions.append(point)
}
}
#IBAction func changeButtonPosition(_ sender: AnyObject) {
UIView.animate(withDuration: 0.35) { [weak self] in
guard let self = self else { return }
self.index += 1
let point = self.positions[self.index % 4]
self.button.frame = CGRect(origin: point, size: CGSize(width: self.width, height: self.height))
}
}
}

Drag a CGRect using UIPanGestureRecognizer

I am subclassing UIView, and am trying to "drag" a CGRect back and forth across the screen. Basically I want to move(redraw) the rectangle every time I drag my finger. So far, I have this code:
var rectangle: CGRect {
get {
return CGRect(x: 200,
y: 200,
width: frame.width / 6,
height: 15)
}
set {}
}
override func draw(_ rect: CGRect) {
let gesture = UIPanGestureRecognizer(target: self, action: #selector(dragRectangle(recognizer:)))
addGestureRecognizer(gesture)
drawRectangle(rect)
}
func drawRectangle(_ rect: CGRect) {
let path = UIBezierPath(rect: rectangle)
UIColor.black.set()
path.fill()
}
#objc func dragRectangle(recognizer: UIPanGestureRecognizer) {
let translation = recognizer.translation(in: self)
rectangle = CGRect(x: rectangle.midX + translation.x, y: rectangle.midY + translation.y, width: rectangle.width, height: rectangle.height)
setNeedsDisplay()
recognizer.setTranslation(CGPoint.zero, in: self)
}
This is my first time using UIPanGestureRecognizer, so I'm not sure of all the details that go into this. I have set breakpoints in drawRectangle and confirmed that this is being called. However, the rectangle on the screen does not move at all, no matter how many times I try to drag it. What's wrong?
This is how you can do it easily. just copy paste and run this.
//
// RootController.swift
// SampleApp
//
// Created by Chanaka Caldera on 24/6/19.
// Copyright © 2019 homeapps. All rights reserved.
//
import UIKit
class RootController: UIViewController {
private var draggableView: UIView!
private var pangesture: UIPanGestureRecognizer!
override func viewDidLoad() {
super.viewDidLoad()
setdragview()
}
}
extension RootController {
fileprivate func setdragview() {
draggableView = UIView()
draggableView.translatesAutoresizingMaskIntoConstraints = false
draggableView.backgroundColor = .lightGray
view.addSubview(draggableView)
let draggableviewConstraints = [draggableView.leftAnchor.constraint(equalTo: view.leftAnchor,constant: 10),
draggableView.topAnchor.constraint(equalTo: view.topAnchor, constant: 64),
draggableView.widthAnchor.constraint(equalToConstant: 100),
draggableView.heightAnchor.constraint(equalToConstant: 40)]
NSLayoutConstraint.activate(draggableviewConstraints)
pangesture = UIPanGestureRecognizer()
draggableView.isUserInteractionEnabled = true
draggableView.addGestureRecognizer(pangesture)
pangesture.addTarget(self, action: #selector(draggableFunction(_:)))
}
}
extension RootController {
#objc fileprivate func draggableFunction(_ sender: UIPanGestureRecognizer) {
view.bringSubviewToFront(draggableView)
let translation = sender.translation(in: self.view)
draggableView.center = CGPoint(x: draggableView.center.x + translation.x, y: draggableView.center.y + translation.y)
sender.setTranslation(CGPoint.zero, in: self.view)
print("drag works : \(translation.x)")
}
}
here is the demo,
Hope this will help. cheers !
Try like this (check comments through code):
#IBDesignable
class Rectangle: UIView {
#IBInspectable var color: UIColor = .clear {
didSet { backgroundColor = color }
}
// draw your view using the background color
override func draw(_ rect: CGRect) {
backgroundColor?.set()
UIBezierPath(rect: rect).fill()
}
// add the gesture recognizer to your view
override func didMoveToSuperview() {
addGestureRecognizer(UIPanGestureRecognizer(target: self, action: #selector(pan)))
}
// your gesture selector
#objc func pan(_ gesture: UIPanGestureRecognizer) {
// update your view frame origin
frame.origin += gesture.translation(in: self)
// reset the gesture translation
gesture.setTranslation(.zero, in: self)
}
}
extension CGPoint {
static func +=(lhs: inout CGPoint, rhs: CGPoint) {
lhs.x += rhs.x
lhs.y += rhs.y
}
}
To draw rectangles on your view when panning you can do as follow:
import UIKit
class ViewController: UIViewController {
var rectangles: [Rectangle] = []
override func viewDidLoad() {
super.viewDidLoad()
view.addGestureRecognizer(UIPanGestureRecognizer(target: self, action: #selector(pan)))
}
#objc func pan(_ gesture: UIPanGestureRecognizer) {
switch gesture.state {
case .began:
let rectangle = Rectangle(frame: .init(origin: gesture.location(in: view), size: .init(width: 0, height: 0)))
rectangle.fillColor = .red
rectangle.strokeColor = .white
rectangle.lineWidth = 3
view.addSubview(rectangle)
rectangles.append(rectangle)
case .changed:
let distance = gesture.translation(in: view)
let index = rectangles.index(before: rectangles.endIndex)
let frame = rectangles[index].frame
rectangles[index].frame = .init(origin: frame.origin, size: .init(width: frame.width + distance.x, height: frame.height + distance.y))
rectangles[index].setNeedsDisplay()
gesture.setTranslation(.zero, in: view)
case .ended:
break
default:
break
}
}
}
Sample Project
I figured out most of the problem as to why the rectangle wasn't moving. It turns out that I misunderstood how variable getters and setters work in Swift. Nevertheless, I changed this code:
var rectangle: CGRect {
get {
return CGRect(x: 200,
y: 200,
width: frame.width / 6,
height: 15)
}
set {}
}
to be a lazy variable:
lazy var rectangle: CGRect = {
return CGRect(x: 200,
y: 200,
width: self.frame.width / 6,
height: 15)
}()
The reason I needed get and set in the first place was because I use frame in my variable calculations, and that was not available until the view itself was fully instantiated. I also tweaked this code a bit:
rectangle = CGRect(x: rectangle.midX + translation.x, y: rectangle.midY + translation.y, width: rectangle.width, height: rectangle.height)
and used minX instead of midX:
rectangle = CGRect(x: rectangle.minX + translation.x, y: rectangle.minY + translation.y, width: rectangle.width, height: rectangle.height)
This is because CGRect is initialized with the x and y parameters being the minX and minY.
This is good progress(at least the rectangle moves). However, I am not able to figure out why the rectangle only switches places after I have released my mouse, resulting in choppy movement.

How to remove programmatically created UIViews from superview

I wanted to implement a loading overlay whilst I have content loading in from an API call however when I go to dismiss the view; I have no success.
func viewLoading(show:Bool, boxView: UIView, error: Bool, errorMessage: String){
let myNewView=UIView(frame: CGRect(x: 0, y: 0, width: boxView.frame.width, height: boxView.frame.height))
if show{
// Change UIView background colour
myNewView.backgroundColor = UIColor.black.withAlphaComponent(0.75)
myNewView.isOpaque = false
// Add rounded corners to UIView
myNewView.layer.cornerRadius = boxView.layer.cornerRadius
let activityView = UIActivityIndicatorView(style: .whiteLarge)
activityView.center = myNewView.center
activityView.startAnimating()
boxView.addSubview(myNewView)
myNewView.addSubview(activityView)
}else{
print("Done")
DispatchQueue.main.async(execute: { () -> Void in
myNewView.removeFromSuperview()
self.view.bringSubviewToFront(boxView)
})
myNewView.isHidden = true
}
}
None of the options after else have worked and I am lost at a solution.
Edit: I want the same function(s) to accommodate three different views within the one view controller.
Move myNewView outside of the viewLoading function scope, and it is better to create separate methods with their own responsibilities, like so:
class ViewController: UIViewController {
var loaderView: UIView?
func showLoading(boxView: UIView, error: Bool, errorMessage: String) {
if (self.loaderView != nil) {
self.hideLoading()
}
let newView = UIView(frame: CGRect(x: 0, y: 0, width: boxView.frame.width, height: boxView.frame.height))
newView.backgroundColor = UIColor.black.withAlphaComponent(0.75)
newView.isOpaque = false
// Add rounded corners to UIView
newView.layer.cornerRadius = boxView.layer.cornerRadius
let activityView = UIActivityIndicatorView(style: .whiteLarge)
activityView.center = newView.center
activityView.startAnimating()
boxView.addSubview(newView)
newView.addSubview(activityView)
self.loaderView = newView
}
func hideLoading() {
guard
let loaderView = self.loaderView,
let boxView = loaderView.superview
else { return }
DispatchQueue.main.async {
loaderView.removeFromSuperview()
self.view.bringSubviewToFront(boxView) // need this?
self.loaderView = nil
}
}
}
You are creating a new view every time that method is called and then you are trying to dismish that newly created view. Instead, you should save a reference of the view when you show it and call removeFromSuperview on that instance when you need to hide it.
Check this..
CommonMethods.swift
import UIKit
class CommonMethods: UIViewController {
static let actInd: UIActivityIndicatorView = UIActivityIndicatorView()
static let container: UIView = UIView()
static let loadingView: UIView = UIView()
static func showActivityIndicatory(uiView: UIView) {
container.frame = uiView.frame
container.center = uiView.center
container.backgroundColor = UIColor(red:255/255, green:255/255, blue:255/255, alpha: 0.3)
loadingView.frame = CGRect(origin: CGPoint(x: 0,y :0), size: CGSize(width: 80, height: 80))
loadingView.center = uiView.center
loadingView.backgroundColor = UIColor(red:44/255, green:44/255, blue:44/255, alpha: 0.7)
loadingView.clipsToBounds = true
loadingView.layer.cornerRadius = 10
actInd.frame = CGRect(origin: CGPoint(x: 0,y :0), size: CGSize(width: 40, height: 40))
actInd.style =
UIActivityIndicatorView.Style.whiteLarge
actInd.center = CGPoint(x: loadingView.frame.size.width / 2, y: loadingView.frame.size.height / 2);
loadingView.addSubview(actInd)
container.addSubview(loadingView)
uiView.addSubview(container)
actInd.startAnimating()
}
static func hideActivityIndicatory(uiView: UIView) {
container.removeFromSuperview()
actInd.stopAnimating()
}
}
call it from viewcontroller class like
CommonMethods.showActivityIndicatory(uiView: self.view)
CommonMethods.hideActivityIndicatory(uiView: self.view)

how to block vertical swipe movement in UIScrollView in Swift?

i have an Image carousel in my app
i use a UIScrollView to show the images inside.
everything works fine, it's just that i want to know how do i block up movements in the UIScrollView
for now the user can just swipe the picture up.. and i want to block it
i tried doing some fixed but none seems to work now maybe it's something in my code ?
here is my complete view controller for the UIScrollView:
class CaruselleScreenViewController: UIViewController, CaruselleScreenViewProtocol, UIScrollViewDelegate {
#IBOutlet weak var pageControl: UIPageControl!
#IBOutlet weak var scrollView: UIScrollView!
var slides:[CaruselleTipsCard] = [];
override func viewDidLoad() {
super.viewDidLoad()
slides = createSlides()
setupSlideScrollView(slides: slides)
pageControl.numberOfPages = slides.count
pageControl.currentPage = 0
view.bringSubview(toFront: pageControl)
//adds dots
scrollView.delegate = self
// Do any additional setup after loading the view.
}
func createSlides() -> [CaruselleTipsCard] {
let slide1:CaruselleTipsCard = Bundle.main.loadNibNamed("CaruselleTipsCard", owner: self, options: nil)?.first as! CaruselleTipsCard
slide1.mainPic.image = UIImage(named: "backlightingIllo")
//
let slide2:CaruselleTipsCard = Bundle.main.loadNibNamed("CaruselleTipsCard", owner: self, options: nil)?.first as! CaruselleTipsCard
slide2.mainPic.image = UIImage(named: "comfortableIllo")
//
let slide3:CaruselleTipsCard = Bundle.main.loadNibNamed("CaruselleTipsCard", owner: self, options: nil)?.first as! CaruselleTipsCard
slide3.mainPic.image = UIImage(named: "pharmacyIllo")
//
let slide4:CaruselleTipsCard = Bundle.main.loadNibNamed("CaruselleTipsCard", owner: self, options: nil)?.first as! CaruselleTipsCard
slide4.mainPic.image = UIImage(named: "batteryIllo")
//
let slide5:CaruselleTipsCard = Bundle.main.loadNibNamed("CaruselleTipsCard", owner: self, options: nil)?.first as! CaruselleTipsCard
slide5.mainPic.image = UIImage(named: "wiFiIllo")
return [slide1, slide2, slide3, slide4, slide5]
}
func setupSlideScrollView(slides : [CaruselleTipsCard]) {
scrollView.frame = CGRect(x: 0, y: 0, width: view.frame.width, height: view.frame.height)
scrollView.contentSize = CGSize(width: view.frame.width * CGFloat(slides.count), height: view.frame.height)
scrollView.isPagingEnabled = true
for i in 0 ..< slides.count {
slides[i].frame = CGRect(x: view.frame.width * CGFloat(i), y: 0, width: view.frame.width, height: view.frame.height)
scrollView.addSubview(slides[i])
}
}
//////
/*
* default function called when view is scolled. In order to enable callback
* when scrollview is scrolled, the below code needs to be called:
* slideScrollView.delegate = self or
*/
func scrollViewDidScroll(_ scrollView: UIScrollView) {
let pageIndex = round(scrollView.contentOffset.x/view.frame.width)
pageControl.currentPage = Int(pageIndex)
let maximumHorizontalOffset: CGFloat = scrollView.contentSize.width - scrollView.frame.width
let currentHorizontalOffset: CGFloat = scrollView.contentOffset.x
// vertical
let maximumVerticalOffset: CGFloat = scrollView.contentSize.height - scrollView.frame.height
let currentVerticalOffset: CGFloat = scrollView.contentOffset.y
let percentageHorizontalOffset: CGFloat = currentHorizontalOffset / maximumHorizontalOffset
let percentageVerticalOffset: CGFloat = currentVerticalOffset / maximumVerticalOffset
/*
* below code changes the background color of view on paging the scrollview
*/
// self.scrollView(scrollView, didScrollToPercentageOffset: percentageHorizontalOffset)
/*
* below code scales the imageview on paging the scrollview
*/
let percentOffset: CGPoint = CGPoint(x: percentageHorizontalOffset, y: percentageVerticalOffset)
if(percentOffset.x > 0 && percentOffset.x <= 0.25) {
slides[0].mainPic.transform = CGAffineTransform(scaleX: (0.25-percentOffset.x)/0.25, y: (0.25-percentOffset.x)/0.25)
slides[1].mainPic.transform = CGAffineTransform(scaleX: percentOffset.x/0.25, y: percentOffset.x/0.25)
} else if(percentOffset.x > 0.25 && percentOffset.x <= 0.50) {
slides[1].mainPic.transform = CGAffineTransform(scaleX: (0.50-percentOffset.x)/0.25, y: (0.50-percentOffset.x)/0.25)
slides[2].mainPic.transform = CGAffineTransform(scaleX: percentOffset.x/0.50, y: percentOffset.x/0.50)
} else if(percentOffset.x > 0.50 && percentOffset.x <= 0.75) {
slides[2].mainPic.transform = CGAffineTransform(scaleX: (0.75-percentOffset.x)/0.25, y: (0.75-percentOffset.x)/0.25)
slides[3].mainPic.transform = CGAffineTransform(scaleX: percentOffset.x/0.75, y: percentOffset.x/0.75)
} else if(percentOffset.x > 0.75 && percentOffset.x <= 1) {
slides[3].mainPic.transform = CGAffineTransform(scaleX: (1-percentOffset.x)/0.25, y: (1-percentOffset.x)/0.25)
slides[4].mainPic.transform = CGAffineTransform(scaleX: percentOffset.x, y: percentOffset.x)
}
}
}
can you tell me how to block vertical swipe ?
You can try something like this,
scrollView.showsVerticalScrollIndicator = false
scrollView.contentSize = CGSize(width: scrollView.contentSize.width, height: 0) //disable vertical

Customized UITextField in Swift 4.0 text overlapped when edit

I am trying to make a textField for username and password.
So far, this is what I have achieved:
Current View on iPhone
However, when editing, everything is good. But then I try to edit again, this happened.
The previous text shifted place back to the origin
Here are the code for the textfield
#IBDesignable class StandardInputTextField: UITextField {
let kInset = Constants.buttonTitleInset
#IBInspectable var leftImage: UIImage? {
didSet {
updateView()
}
}
#IBInspectable var leftPadding: CGFloat = 0 {
didSet {
updateView()
}
}
fileprivate func updateView() {
layer.borderColor = UIColor.clear.cgColor
if let leftImage = leftImage {
leftViewMode = .always
let imageView = UIImageView(image: leftImage)
if frame.height >= 20 {
imageView.frame = CGRect(x: kInset,
y: kInset / 2.0,
width: frame.height - 2 * kInset,
height: frame.height - 2 * kInset)
} else {
imageView.frame = CGRect(x: 0, y: 0,
width: frame.height,
height: frame.height)
}
var containerWidth =
imageView.frame.width + 2 * kInset
if borderStyle == .none ||
borderStyle == .line {
containerWidth += 5
}
let imageViewContainer = UIView(frame:
CGRect(x: 0, y: 0,
width: containerWidth,
height: imageView.frame.height + 2 * kInset))
imageViewContainer.addSubview(imageView)
leftView = imageViewContainer
} else {
leftViewMode = .never
}
}
override func draw(_ rect: CGRect) {
super.drawText(in: rect)
if leftViewMode == .always {
guard let leftViewRect = leftView?.frame else {
return
}
let linePath = UIBezierPath()
linePath.move(to:
CGPoint(x: leftViewRect.minX,y: rect.maxY))
linePath.addLine(to:
CGPoint(x: rect.maxX, y: rect.maxY))
Colors.ButtonColor.set()
linePath.lineWidth = 2.0
linePath.stroke()
}
}
}
So did this happen to anyone? and how to fix this? Much appreciated.
Apparently, this happens when I do this :
override func draw(_ rect: CGRect) {
super.draw(rect)
}
So the solution is simply remove
super.draw(rect)
That's it, enjoy