I've ImageView placed inside UICollectionViewCell and a PageControl below it. Both are connected via programmatically.
However, I want auto swipe (say every 5 seconds) the images should auto swipe. If user swipe manually then too it should happen. Also, when page control reaches to last it should begin with first.
Below is code that I've used :
var offersImages: [UIImage] = [ //Array of Banners
UIImage(named: "default-banner.png")!,
UIImage(named: "default-banner.png")!,
UIImage(named: "default-banner.png")!,
UIImage(named: "default-banner.png")!,
]
override func viewDidLoad() {
super.viewDidLoad()
self.objPageControl.currentPage = 0
self.objPageControl.numberOfPages = self.offersImages.count
}
func scrollViewDidScroll(scrollView: UIScrollView) {
let pageWidth : CGFloat = self.objBannerCollectionView.frame.size.width
self.objPageControl.currentPage = Int(self.objBannerCollectionView.contentOffset.x/pageWidth)
}
How can this be done in Swift?
Refer following link , it will help to resolve your problem.
Horizontal Paging
Below code solved my problem :
//MARK:- viewDidLoad
override func viewDidLoad() {
super.viewDidLoad()
. . .
_ = NSTimer.scheduledTimerWithTimeInterval(2, target: self, selector: #selector(moveToNextPage), userInfo: nil, repeats: true)
}
func moveToNextPage (){
let pageWidth:CGFloat = self.objBannerCollectionView.frame.width
let maxWidth:CGFloat = pageWidth * 4
let contentOffset:CGFloat = self.objBannerCollectionView.contentOffset.x
var slideToX = contentOffset + pageWidth
if contentOffset + pageWidth == maxWidth {
slideToX = 0
}
self.objBannerCollectionView.scrollRectToVisible(CGRect(x:slideToX, y:0, width:pageWidth, height:self.objBannerCollectionView.frame.height), animated: true)
}
Related
My first question on SO so bear with me. I have created a UICollectionViewController which has a header and 1 cell. Inside the cell is a tableview, inside the table view there are multiple static cells. One of those has a horizontal UICollectionView with cells which have UITextViews.
Problem: When tapping on a UITextView the collection view scrolls/jumps
Problem Illustration
On the right you can see the y offset values. On first tap it changes to 267 -- the header hight. On a consecutive tap it goes down to 400 -- the very bottom. This occurs no matter what I tried to do.
Note: Throughout my app I'am using IQKeyboardManager
What have I tried:
Disabling IQKeyboardManager completely and
Taping on text view
Replacing it with a custom keyboard management methods based on old SO answers
Set collectionView.shouldIgnoreScrollingAdjustment = true for:
all scrollable views in VC
Individuals scrollable views
Note: this property originates from the IQKeyboardManager Library and as far as I understand it is supposed to disable scroll adjustment offset.
Tried disabling scroll completely in viewDidLoad() as well as all other places within this VC. I used:
collectionView.isScrollEnabled = false
collectionView.alwaysBounceVertical = false
Notably, I have tried disabling scroll in text viewDidBeginEditing as well as the custom keyboard management methods.
My Code:
The main UICollectionView and its one cell are created in the storyboard. Everything else is done programatically. Here is the flow layout function that dictates the size of the one and only cell:
extension CardBuilderCollectionViewController: UICollectionViewDelegateFlowLayout {
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout:
UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let height = view.frame.size.height
let width = view.frame.size.width
return CGSize(width: width * cellWidthScale, height: height * cellHeigthScale)
}
}
Additionally, collectionView.contentInsetAdjustmentBehavior = .never
The TableView within the subclass of that one cell is created like so:
let tableView: UITableView = {
let table = UITableView()
table.estimatedRowHeight = 300
table.rowHeight = UITableView.automaticDimension
return table
}()
and:
override func awakeFromNib() {
super.awakeFromNib()
dataProvider = DataProvider(delegate: delegate)
addSubview(tableView)
tableView.fillSuperview() // Anchors to 4 corners of superview
registerCells()
tableView.delegate = dataProvider
tableView.dataSource = dataProvider
}
The cells inside the table view are all subclasses of class GeneralTableViewCell, which contains the following methods which determine the cells height:
var cellHeightScale: CGFloat = 0.2 {
didSet {
setContraints()
}
}
private func setContraints() {
let screen = UIScreen.main.bounds.height
let heightConstraint = heightAnchor.constraint(equalToConstant: screen*cellHeightScale)
heightConstraint.priority = UILayoutPriority(999)
heightConstraint.isActive = true
}
The height of the nested cells (with TextView) residing in the table view is determined using the same method as the one and only cell in the main View.
Lastly the header is created using a custom FlowLayout:
class StretchyHeaderLayout: UICollectionViewFlowLayout {
override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
let layoutAttributes = super.layoutAttributesForElements(in: rect)
layoutAttributes?.forEach({ (attribute) in
if attribute.representedElementKind == UICollectionView.elementKindSectionHeader && attribute.indexPath.section == 0 {
guard let collectionView = collectionView else { return }
attribute.zIndex = -1
let width = collectionView.frame.width
let contentOffsetY = collectionView.contentOffset.y
print(contentOffsetY)
if contentOffsetY > 0 { return }
let height = attribute.frame.height - contentOffsetY
attribute.frame = CGRect(x: 0, y: contentOffsetY, width: width, height: height)
}
})
return layoutAttributes
}
override func shouldInvalidateLayout(forBoundsChange newBounds: CGRect) -> Bool {
return true
}
}
This is my first time designing a complex layout with mostly a programatic approach. Hence it is possible that I missed something obvious. However, despite browsing numerous old questions I was not able to find a solution. Any solutions or guidance is appreciated.
Edit:
As per request here are the custom keyboard methods:
In viewDidLoad()
NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillShow), name: UIResponder.keyboardWillChangeFrameNotification, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillHide), name: UIResponder.keyboardWillHideNotification, object: nil)
Then:
var scrollOffset : CGFloat = 0
var distance : CGFloat = 0
var activeTextFeild: UITextView?
var safeArea: CGRect?
#objc func keyboardWillShow(notification: NSNotification) {
if let keyboardSize = (notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue {
var safeArea = self.view.frame
safeArea.size.height += collectionView.contentOffset.y
safeArea.size.height -= keyboardSize.height + (UIScreen.main.bounds.height*0.04)
self.safeArea = safeArea
}
}
private func configureScrollView() {
if let activeField = activeTextFeild {
if safeArea!.contains(CGPoint(x: 0, y: activeField.frame.maxY)) {
print("No need to Scroll")
return
} else {
distance = activeField.frame.maxY - safeArea!.size.height
scrollOffset = collectionView.contentOffset.y
self.collectionView.setContentOffset(CGPoint(x: 0, y: scrollOffset + distance), animated: true)
}
}
// prevent scrolling while typing
collectionView.isScrollEnabled = false
collectionView.alwaysBounceVertical = false
}
#objc func keyboardWillHide(notification: NSNotification) {
if distance == 0 {
return
}
// return to origin scrollOffset
self.collectionView.setContentOffset(CGPoint(x: 0, y: scrollOffset), animated: true)
scrollOffset = 0
distance = 0
collectionView.isScrollEnabled = true
}
Finaly:
//MARK: - TextViewDelegate
extension CardBuilderCollectionViewController: UITextViewDelegate {
func textViewDidBeginEditing(_ textView: UITextView) {
self.activeTextFeild = textView
configureScrollView()
}
}
The problem that I can see is you calling configureScrollView() when your textView is focused in textViewDidBeginEditing .
distance = activeField.frame.maxY - safeArea!.size.height
scrollOffset = collectionView.contentOffset.y
self.collectionView.setContentOffset(CGPoint(x: 0, y: scrollOffset + distance), animated: true)
You're calling collectionView.setContentOffset --> so that's why your collection view jumping.
Please check your distance calculated correctly or not. Also, your safeArea was modified when keyboardWillShow.
Try to disable setCOntentOffset?
I'm trying to update text of a label after a scroll event. I have a print command that prints the correct value but the label is not updating.
Here's my code
func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
let x = scrollView.contentOffset.x
let w = scrollView.bounds.size.width
let p = Int(x/w)
print("page \(p)") // this prints correct value
self.signalLabel.text = signalText[Int(x/w)] // this does not update
}
what's the deal?
Here's the complete view controller code. This view is called from a button click on the initial view controller. This view contains a UIScrollView and UIPageControl. The UIScrollView contains two images that can be scrolled back and forth. I want to update the label text based on image that is shown.
import UIKit
class SignalOneViewController: UIViewController, UIScrollViewDelegate {
// MARK: Properties
#IBOutlet weak var signalScrollView: UIScrollView!
#IBOutlet weak var signalPageControl: UIPageControl!
#IBOutlet weak var signalLabel: UILabel!
// MARK: - Button Actions
#IBAction func signalOneButton(_ sender: Any) {
print("signal one button clicked")
performSegue(withIdentifier: "SignalOneSegue", sender: self)
}
#IBAction func onCancelButton(_ sender: Any) {
dismiss(animated: true, completion: nil)
}
let signalImages = ["signal1a.png", "signal1b.png"]
let signalText = ["Ready for play", "Untimed down"]
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
override func viewDidLayoutSubviews() {
self.loadScrollView()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func loadScrollView() {
let pageCount : CGFloat = CGFloat(signalImages.count)
signalLabel.text = signalText[0]
signalScrollView.backgroundColor = UIColor.clear
signalScrollView.delegate = self
signalScrollView.isPagingEnabled = true
signalScrollView.contentSize = CGSize(width: signalScrollView.frame.size.width * pageCount, height: signalScrollView.frame.size.height)
signalScrollView.showsHorizontalScrollIndicator = false
signalScrollView.showsVerticalScrollIndicator = false
signalPageControl.numberOfPages = Int(pageCount)
signalPageControl.pageIndicatorTintColor = UIColor.lightGray
signalPageControl.currentPageIndicatorTintColor = UIColor.blue
signalPageControl.addTarget(self, action: #selector(self.pageChanged), for: .valueChanged)
for i in 0..<Int(pageCount) {
print(self.signalScrollView.frame.size.width)
let image = UIImageView(frame: CGRect(x: self.signalScrollView.frame.size.width * CGFloat(i), y: 0, width: self.signalScrollView.frame.size.width, height: self.signalScrollView.frame.size.height))
image.image = UIImage(named: signalImages[i])!
image.contentMode = UIViewContentMode.scaleAspectFit
self.signalScrollView.addSubview(image)
}
}
//MARK: UIScrollView Delegate
func scrollViewDidScroll(_ scrollView: UIScrollView) {
let viewWidth: CGFloat = scrollView.frame.size.width
// content offset - tells by how much the scroll view has scrolled.
let pageNumber = floor((scrollView.contentOffset.x - viewWidth / 50) / viewWidth) + 1
signalPageControl.currentPage = Int(pageNumber)
}
func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
let x = scrollView.contentOffset.x
let w = scrollView.bounds.size.width
let p = Int(x/w)
print("page \(p)")
self.signalLabel.text = signalText[p]
print(">>> \(signalText[Int(x/w)])")
}
//MARK: page tag action
#objc func pageChanged() {
let pageNumber = signalPageControl.currentPage
var frame = signalScrollView.frame
frame.origin.x = frame.size.width * CGFloat(pageNumber)
frame.origin.y = 0
signalScrollView.scrollRectToVisible(frame, animated: true)
}
}
Make sure signalLabe IBOutlet is attached to your label in storyboard or xib
I have an array of images if I pressed the button the image will randomly appear. and there is an image down also called from the array but it appears randomly just once I opened the game. I want to do a conditions statement for button pressed. like I have 5 conditions once the button clicked: 1- if pressed and the image appeared is not same as the UIIMage view down score will be added
2- if button did not press for 2 seconds will appear down.
3- if button pressed and it is same as the UIimage so game over.
4- If he calculated 4 images down because he didn't hit the image in 2 seconds he will lose.
var array:[UIImage] = [UIImage(named: "1.png")!,
UIImage(named: "2.png")!,
UIImage(named: "3.png")!,
UIImage(named: "4.png")!,
UIImage(named: "5.png")!,
UIImage(named: "6.png")!,
UIImage(named: "7.png")!,
UIImage(named: "8.png")!,
UIImage(named: "9.png")!,
UIImage(named: "10.png")!]
var random = arc4random_uniform(10)
#IBAction func myButtonPressed(button: UIButton) {
var randomNum: UInt32 = 10
randomNum = arc4random_uniform(UInt32(array.count))
myButton.setImage(UIImage(named: "bird\(randomNum).png"), for: UIControlState.normal)
// myButton.setImage(UIImage(named: "\(randomNum).png"), for: UI)
self.myImage.animationImages = array
let buttonWidth = myButton.frame.width
let buttonHeight = myButton.frame.height
// Find the width and height of the enclosing view
let viewWidth = myButton.superview!.bounds.width
let viewHeight = myButton.superview!.bounds.height
// Compute width and height of the area to contain the button's center
let xwidth = viewWidth - buttonWidth
let yheight = viewHeight - buttonHeight
// Generate a random x and y offset
let xoffset = CGFloat(arc4random_uniform(UInt32(xwidth)))
let yoffset = CGFloat(arc4random_uniform(UInt32(yheight)))
// Offset the button's center by the random offsets.
myButton.center.x = xoffset + buttonWidth / 2
myButton.center.y = yoffset + buttonHeight / 2
/* for i in array{
if myButton != myImage{
randomNum = arc4random_uniform(UInt32(array.count))
}
if else
} */
}
Perhaps you could try something like this. As for your first condition, what is your UIImage View Variable. Is it another UIImage View that is being displayed on the storyboard?
import UIKit
class ViewController: UIViewController {
var array: [UIImage] = [UIImage(named: "1.png")!,
UIImage(named: "2.png")!,
UIImage(named: "3.png")!,
UIImage(named: "4.png")!,
UIImage(named: "5.png")!]
#IBOutlet weak var myButton: UIButton!
var pressed: Bool = false;
var score: Int = 0;
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
//2nd Condition: if button did not press for 2 seconds will appear down
if(pressed == false){
Timer.scheduledTimer(timeInterval: 0.2, target: self, selector: #selector(ViewController.doSomething), userInfo: nil, repeats: true)
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
#IBAction func isPressed(_ sender: UIButton) {
pressed = true;
var randomNum: UInt32 = 10
randomNum = arc4random_uniform(UInt32(array.count));
myButton.setImage(UIImage(named: "\(randomNum).png"), for: .normal)
// 1st Condition: if pressed and the image appeared is not same as the UIIMage view down score will be added
if( UIImage(named: "\(randomNum).png") != myButton.currentImage){
//Add View Down Score
score += 1;
}
// 3rd Condition: if button pressed and it is same as the UIimage so game over
else{
// Game Over
}
}
func doSomething() {
// Not sure what you mean by will appear down
print("Action")
}
}
I am not quite sure what your 4th condition does. Please clarify
Currently, I have a CustomTableViewCell that is used in four or 5 different places. The custom cell has a lazy loaded UILongPressGestureRecognizer property that gets added as a gesture recognizer in cellForRowAtIndexPath in the parent VC.
self.tableView.addGestureRecognizer(cell.longPress)
When a user initiates the long press, I want a toast notification to popup displaying some contextual information, and then to disappear after a few seconds. I've included this in my code for the CustomTableViewCell, but all of these decisions are starting to "smell." Is there a smarter, more logical way to be implementing these decisions?
This table view cell has the following code:
weak var parentTableView: UITableView?
lazy var longPress: UILongPressGestureRecognizer = {
let longPress = UILongPressGestureRecognizer(target: self, action: #selector(longPressSelector))
longPress.minimumPressDuration = 0.5
longPress.delegate = self
return longPress
}()
func longPressSelector(_ longPress: UILongPressGestureRecognizer!) {
if let tableView = self.parentTableView {
let point = longPress.location(in: tableView)
let indexPath = tableView.indexPathForRow(at: point)
if ((indexPath! as NSIndexPath).section == 0 && longPress.state == .began) {
// do some work
// Show informational popup
let toast = createToastNotification(withTitle: addedSong.name)
Timer.scheduledTimer(withTimeInterval: 2.0, repeats: false) { (timer) -> Void in
UIView.animate(withDuration: 1.0) { () -> Void in
toast.alpha = 0.0
toast = nil
}
}
}
}
}
func createToastNotification(withTitle title: String) -> UIView {
if let tableView = self.parentTableView {
let windowFrame = tableView.superview?.bounds
let height:CGFloat = 145, width: CGFloat = 145
let x = (windowFrame?.width)! / 2 - width / 2
let y = (windowFrame?.height)! / 2 - height / 2
let toast = EnsembleToastView.create()
toast.frame = CGRect(x: x, y: y, width: width, height: height)
toast.songLabel.text = title
toast.layer.cornerRadius = 5
tableView.superview?.addSubview(toast)
return toast
}
return UIView()
}
I think it makes more sense for the TableView to know how to display a toast so I would create a protocol in your tableViewCell, so I would take the following steps.
Make the TableViewController responsible for:
creating toast (only once)
configuring toast
showing toast
responding to long press gesture
configuring your table view cell
Allow YourTableViewCell to only delegate
So let's do responding to long press gesture first
protocol TableViewCellLongPressDelegate {
func tableViewCellHadLongPress(_ cell: YourTableViewCell)
}
Then extend your TableViewController to conform to your new protocol
extension YourTableViewController : TableViewCellLongPressDelegate {
func tableViewCellHadLongPress(_ cell: YourTableViewCell){
//configure toast based on which cell long pressed
configureToastNotification(with title: cell.title){
//show toast
}
}
Now, configuring your table view cell within your TableViewController configure your cell and assign the TableViewController as the longPressDelegate
let cell = YourTableViewCell.dequeue(from: self.tableView)!
//configure cell
cell.tableViewCellLongPressDelegate = self
This approach is nice because you can move the createToastNotification() method to your TableViewController and be responsible for creating toast (only once)
var toastNotification : UIView?
viewDidLoad(){
//yatta yatta
toastNotification = createToastNotification()
}
Then you can change createToastNotification to
func createToastNotification() -> UIView? {
let windowFrame = self.bounds
let height:CGFloat = 145, width: CGFloat = 145
let x = (windowFrame?.width)! / 2 - width / 2
let y = (windowFrame?.height)! / 2 - height / 2
let toast = EnsembleToastView.create()
toast.frame = CGRect(x: x, y: y, width: width, height: height)
toast.layer.cornerRadius = 5
self.addSubview(toast)
return toast
}
Lastly for YourTableViewController, configuring toast, let's create a configureToastNotification(with title: String) like:
func configureToastNotification(with title: String){
if let toast = self.toastNotification {
toast.songLabel.text = title
}
}
For the end, we remove a lot of the responsibility from YourTableViewCell and allow it to only delegate :
protocol TableViewCellLongPressDelegate : class {
func tableViewCellHadLongPress(_ cell: YourTableViewCell)
}
class YourTableViewCell : UITableViewCell {
//initializers
weak var longPressDelegate: TableViewCellLongPressDelegate?
lazy var longPress: UILongPressGestureRecognizer = {
let longPress = UILongPressGestureRecognizer(target: self, action: #selector(longPressHappened))
longPress.minimumPressDuration = 0.5
longPress.delegate = self
return longPress
}()
func longPressHappened() {
self.longPressDelegate?.tableViewCellHadLongPress(self)
}
}
i have a test project that takes text from a file, adds it to a textview and displays it.
i want to add some gestures but cannot seem to make it work...
here is the relevant code:
class ViewController2: UIViewController, UIGestureRecognizerDelegate {
#IBOutlet var textview1: UITextView!
var pinchGesture = UIPinchGestureRecognizer()
override func viewDidLoad() {
super.viewDidLoad()
self.textview1.userInteractionEnabled = true
self.textview1.multipleTouchEnabled = true
self.pinchGesture.delegate = self
self.pinchGesture = UIPinchGestureRecognizer(target: self, action: #selector(ViewController2.pinchRecognized(_:)))
self.view.addGestureRecognizer(self.pinchGesture)
}
#IBAction func pinchRecognized(pinch: UIPinchGestureRecognizer) {
self.textview1.addGestureRecognizer(pinchGesture)
self.textview1.transform = CGAffineTransformScale(self.textview1.transform, pinch.scale, pinch.scale)
pinch.scale = 1.0
}
any ideas? followed several tutorials but none seem to help. code is tested on actual iPhone...
thanks a lot
Edit for Solution:
#IBAction func pinchRecognized(pinch: UIPinchGestureRecognizer) {
var pinchScale = pinchGesture.scale
pinchScale = round(pinchScale * 1000) / 1000.0
if (pinchScale < 1) {
self.textview1.font = UIFont(name: self.textview1.font!.fontName, size: self.textview1.font!.pointSize - pinchScale)
pinchScale = pinchGesture.scale
} else {
self.textview1.font = UIFont(name: self.textview1.font!.fontName, size: self.textview1.font!.pointSize + pinchScale)
pinchScale = pinchGesture.scale
}
}
thanks to nishith Singh
Try adding the gesture recogniser to your textview in viewDidLoad instead of adding it in pinchRecognized. Currently you are adding the pinchGesture to your view which is behind your text view and hence will not receive the touch
var pinchGesture = UIPinchGestureRecognizer()
Use this code:
override func viewDidLoad() {
super.viewDidLoad()
self.textview1.userInteractionEnabled = true
self.textview1.multipleTouchEnabled = true
self.pinchGesture = UIPinchGestureRecognizer(target: self, action:#selector(pinchRecognized(_:)))
self.textview1.addGestureRecognizer(self.pinchGesture)
// Do any additional setup after loading the view.
}
#IBAction func pinchRecognized(_ pinch: UIPinchGestureRecognizer) {
let fontSize = self.textview1.font!.pointSize*(pinch.scale)/2
if fontSize > 12 && fontSize < 32{
textview1.font = UIFont(name: self.textview1.font!.fontName, size:fontSize)
}
}
You might have to hit and trial with the minimum and maximum font sizes as you want, right now the minimum font size is 12 and the maximum font size is 32.
let pinchGesture = UIPinchGestureRecognizer(target: self, action:#selector(self.pinchGesture))
func pinchGesture(sender: UIPinchGestureRecognizer){
sender.view?.transform = (sender.view?.transform)!.scaledBy(x: sender.scale, y: sender.scale)
sender.scale = 1
print("pinch gesture")
}
class ViewController2: UIViewController, UIGestureRecognizerDelegate {
#IBOutlet var textview1: UITextView!
var pinchGesture = UIPinchGestureRecognizer()
override func viewDidLoad() {
super.viewDidLoad()
self.textview1.userInteractionEnabled = true
self.textview1.multipleTouchEnabled = true
self.pinchGesture.delegate = self
self.pinchGesture = UIPinchGestureRecognizer(target: self, action: "pinchRecognized:")
self.view.addGestureRecognizer(self.pinchGesture)
}
func pinchRecognized(pinch: UIPinchGestureRecognizer) {
self.textview1.addGestureRecognizer(pinchGesture)
self.textview1.transform = CGAffineTransformScale(self.textview1.transform, pinch.scale, pinch.scale)
pinch.scale = 1.0
}
Your code is actually working. But not the way you want probably.
Initially you assigned the gesture recogniser to the view of view controller.
But then inside the method you added same gesture recogniser to the UITextView.
So it should be working on UITextView. And the gesture recogniser is removed from the view controller's view. Gesture recogniser can only have one target. Pick view controller's view or textview.
You set the delegate of self.pinchGesture before initialising.
Initialise the self.pinchGesture first.
Set the delegate.
Add self.pinchGesture to self.view
self.pinchGesture.delegate = self
self.pinchGesture = UIPinchGestureRecognizer(target: self, action:#selector(ViewController2.pinchRecognized(_:)))
self.view.addGestureRecognizer(self.pinchGesture)