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
Related
Looking to add a tap gesture to an array of UIViews - without success. Tap seems not to be recognised at this stage.
In the code (extract) below:
Have a series of PlayingCardViews (each a UIView) showing on the main view.
Brought together as an array: cardView.
Need to be able to tap each PlayingCardView independently (and then to be able to identify which one was tapped).
#IBOutlet private var cardView: [PlayingCardView]!
override func viewDidLoad() {
super.viewDidLoad()
let tap = UITapGestureRecognizer(target: self, action: #selector(tapCard(sender: )))
for index in cardView.indices {
cardView[index].isUserInteractionEnabled = true
cardView[index].addGestureRecognizer(tap)
cardView[index].tag = index
}
}
#objc func tapCard (sender: UITapGestureRecognizer) {
if sender.state == .ended {
let cardNumber = sender.view.tag
print("View tapped !")
}
}
You need
#objc func tapCard (sender: UITapGestureRecognizer) {
let clickedView = cardView[sender.view!.tag]
print("View tapped !" , clickedView )
}
No need to check state here as the method with this gesture type is called only once , also every view should have a separate tap so create it inside the for - loop
for index in cardView.indices {
let tap = UITapGestureRecognizer(target: self, action: #selector(tapCard(sender: )))
I will not recommend the selected answer. Because creating an array of tapGesture doesn't make sense to me in the loop. Better to add gesture within PlaycardView.
Instead, such layout should be designed using UICollectionView. If in case you need to custom layout and you wanted to use scrollView or even UIView, then the better approach is to create single Gesture Recognizer and add to the superview.
Using tap gesture, you can get the location of tap and then you can get the selectedView using that location.
Please refer to below example:
import UIKit
class PlayCardView: UIView {
override init(frame: CGRect) {
super.init(frame: frame)
backgroundColor = UIColor.red
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
backgroundColor = UIColor.red
}
}
class SingleTapGestureForMultiView: UIViewController {
var viewArray: [UIView]!
var scrollView: UIScrollView!
override func viewDidLoad() {
super.viewDidLoad()
scrollView = UIScrollView(frame: UIScreen.main.bounds)
view.addSubview(scrollView)
let tapGesture = UITapGestureRecognizer(target: self,
action: #selector(tapGetsure(_:)))
scrollView.addGestureRecognizer(tapGesture)
addSubviews()
}
func addSubviews() {
var subView: PlayCardView
let width = UIScreen.main.bounds.width;
let height = UIScreen.main.bounds.height;
let spacing: CGFloat = 8.0
let noOfViewsInARow = 3
let viewWidth = (width - (CGFloat(noOfViewsInARow+1) * spacing))/CGFloat(noOfViewsInARow)
let viewHeight = (height - (CGFloat(noOfViewsInARow+1) * spacing))/CGFloat(noOfViewsInARow)
var yCordinate = spacing
var xCordinate = spacing
for index in 0..<20 {
subView = PlayCardView(frame: CGRect(x: xCordinate, y: yCordinate, width: viewWidth, height: viewHeight))
subView.tag = index
xCordinate += viewWidth + spacing
if xCordinate > width {
xCordinate = spacing
yCordinate += viewHeight + spacing
}
scrollView.addSubview(subView)
}
scrollView.contentSize = CGSize(width: width, height: yCordinate)
}
#objc
func tapGetsure(_ gesture: UITapGestureRecognizer) {
let location = gesture.location(in: scrollView)
print("location = \(location)")
var locationInView = CGPoint.zero
let subViews = scrollView.subviews
for subView in subViews {
//check if it subclass of PlayCardView
locationInView = subView.convert(location, from: scrollView)
if subView.isKind(of: PlayCardView.self) {
if subView.point(inside: locationInView, with: nil) {
// this view contains that point
print("Subview at \(subView.tag) tapped");
break;
}
}
}
}
}
You can try to pass the view controller as parameter to the views so they can call a function on parent view controller from the view. To reduce memory you can use protocols. e.x
protocol testViewControllerDelegate: class {
func viewTapped(view: UIView)
}
class testClass: testViewControllerDelegate {
#IBOutlet private var cardView: [PlayingCardView]!
override func viewDidLoad() {
super.viewDidLoad()
for cardView in self.cardView {
cardView.fatherVC = self
}
}
func viewTapped(view: UIView) {
// the view that tapped is passed ass parameter
}
}
class PlayingCardView: UIView {
weak var fatherVC: testViewControllerDelegate?
override func awakeFromNib() {
super.awakeFromNib()
let gr = UITapGestureRecognizer(target: self, action: #selector(self.viewDidTap))
self.addGestureRecognizer(gr)
}
#objc func viewDidTap() {
fatherVC?.viewTapped(view: self)
}
}
I have VPMOTPView custom view in my `.xib' like this.
class VerifyOTP: UIView {
#IBOutlet weak var otpView: VPMOTPView!
var emailID = ""
var userID = ""
#IBAction func resendOTPAction(_ sender: UIButton) {
print("asdds")
}
override func awakeFromNib() {
super.awakeFromNib()
otpView.otpFieldsCount = 4
otpView.otpFieldDefaultBorderColor = UIColor.blue
otpView.otpFieldEnteredBorderColor = UIColor.red
otpView.otpFieldBorderWidth = 2
otpView.delegate = self
// Create the UI
otpView.initalizeUI()
}
}
extension VerifyOTP: VPMOTPViewDelegate {
func hasEnteredAllOTP(hasEntered: Bool) {
print("Has entered all OTP? \(hasEntered)")
}
func enteredOTP(otpString: String) {
print("OTPString: \(otpString)")
}
}
Then in my 'ViewController'
var verifyOTPView: VerifyOTP?
self.verifyOTPView = VerifyOTP.fromNib()
self.verifyOTPView?.frame = CGRect(x: 20, y: 0, width: screenSize.width - 40, height: screenSize.height / 3)
self.view.addSubview(self.verifyOTPView!)
But by this I can see my RESEND OTP button on screen but OTPVIEW is not displayed.
From the screenshot it doesn't look like you have any constraints set up for your views?
If not try adding constraints and see if that fixes the problem.
whenever I click a textfield inside the view, then click the other text field, the view disappears. Strange... Can anyone help?
I animate the view using facebook pop. Here is my animation engine code:
import UIKit
import pop
class AnimationEngine {
class var offScreenRightPosition: CGPoint {
return CGPoint(x: UIScreen.main.bounds.width + 250,y: UIScreen.main.bounds.midY - 75)
}
class var offScreenLeftPosition: CGPoint{
return CGPoint(x: -UIScreen.main.bounds.width,y: UIScreen.main.bounds.midY - 75)
}
class var offScreenTopPosition: CGPoint{
return CGPoint(x: UIScreen.main.bounds.midX,y: -UIScreen.main.bounds.midY)
}
class var screenCenterPosition: CGPoint {
return CGPoint(x: UIScreen.main.bounds.midX, y: UIScreen.main.bounds.midY - 75)
}
let ANIM_DELAY : Int = 1
var originalConstants = [CGFloat]()
var constraints: [NSLayoutConstraint]!
init(constraints: [NSLayoutConstraint]) {
for con in constraints {
originalConstants.append(con.constant)
con.constant = AnimationEngine.offScreenRightPosition.x
}
self.constraints = constraints
}
func animateOnScreen(_ delay: Int) {
let time = DispatchTime.now() + Double(Int64(Double(delay) * Double(NSEC_PER_SEC))) / Double(NSEC_PER_SEC)
DispatchQueue.main.asyncAfter(deadline: time) {
var index = 0
repeat {
let moveAnim = POPSpringAnimation(propertyNamed: kPOPLayoutConstraintConstant)
moveAnim?.toValue = self.originalConstants[index]
moveAnim?.springBounciness = 8
moveAnim?.springSpeed = 8
if (index < 0) {
moveAnim?.dynamicsFriction += 10 + CGFloat(index)
}
let con = self.constraints[index]
con.pop_add(moveAnim, forKey: "moveOnScreen")
index += 1
} while (index < self.constraints.count)
}
}
class func animateToPosisition(_ view: UIView, position: CGPoint, completion: ((POPAnimation?, Bool) -> Void)!) {
let moveAnim = POPSpringAnimation(propertyNamed: kPOPLayerPosition)
moveAnim?.toValue = NSValue(cgPoint: position)
moveAnim?.springBounciness = 8
moveAnim?.springSpeed = 8
moveAnim?.completionBlock = completion
view.pop_add(moveAnim, forKey: "moveToPosition")
}
}
Then here is my viewcontroller code where the view is inside in:
import UIKit
import pop
class LoginVC: UIViewController, UITextFieldDelegate {
override var prefersStatusBarHidden: Bool {
return true
}
#IBOutlet weak var emailLoginVCViewConstraint: NSLayoutConstraint!
#IBOutlet weak var emailLoginVCView: MaterialView!
#IBOutlet weak var emailAddressTextField: TextFieldExtension!
#IBOutlet weak var passwordTextField: TextFieldExtension!
var animEngine : AnimationEngine!
override func viewDidAppear(_ animated: Bool) {
self.emailLoginVCView.isUserInteractionEnabled = true
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
self.view.bringSubview(toFront: emailAddressTextField)
self.animEngine = AnimationEngine(constraints: [emailLoginVCViewConstraint])
self.emailAddressTextField.delegate = self
self.passwordTextField.delegate = self
emailAddressTextField.allowsEditingTextAttributes = false
}
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
if (textField === emailAddressTextField) {
passwordTextField.becomeFirstResponder()
} else if (textField === passwordTextField) {
passwordTextField.resignFirstResponder()
} else {
// etc
}
return true
}
#IBAction func emailTapped(_ sender: AnyObject) {
AnimationEngine.animateToPosisition(emailLoginVCView, position: AnimationEngine.screenCenterPosition, completion: { (POPAnimation, Bool)
in
})
}
#IBAction func exitTapped(_ sender: AnyObject) {
AnimationEngine.animateToPosisition(emailLoginVCView, position: AnimationEngine.offScreenRightPosition, completion: { (POPAnimation, Bool)
in
})
}
}
Last here is my hierchy and options: (my view's name is emailLoginVCView). Also when I was debugging when I clicked another textfield I set a breakpoint so I got this info: enter image description here
I have a constraint that binds the center of the login view with the center of the main screen
when I create the AnimationEngine,I pass it that constraint, and it sets its constant to be the offScreenRightPosition.x
when I bring up the email login sheet, I'm not changing the constant of the constraint; I'm just changing the position of the view
which means that autolayout thinks it’s supposed to still be offscreen
when the second textfield becomes active, that’s somehow triggering auto-layout to re-evaluate the constraints, and it sees that the login view’s position doesn’t match what the constraint says it should be so....
Autolayout moves it offscreen
So if I add this in emailTapped(_:), the problem goes away :)
#IBAction func emailTapped(_ sender: AnyObject) {
AnimationEngine.animateToPosisition(emailLoginVCView, position: AnimationEngine.screenCenterPosition, completion: { (POPAnimation, Bool)
in
self.emailLoginVCViewConstraint.constant = 0
})
}
Why my current page in Page Control does not show correct output?
Page 1 and Page 2 display in one dot? Images here:
http://i.stack.imgur.com/498ap.png, http://i.stack.imgur.com/41kdg.png
Last page is page 6 display in dot 5th, doesn't last dot? Image: http://i.stack.imgur.com/NP9u1.png
My code here:
#IBOutlet weak var scrollView: UIScrollView!
#IBOutlet weak var pageControl: UIPageControl!
let totalPages = 6
let sampleBGColors: Array<UIColor> = [UIColor.redColor(), UIColor.yellowColor(), UIColor.greenColor(), UIColor.magentaColor(), UIColor.orangeColor(), UIColor.lightGrayColor()] #IBOutlet weak var scrollView: UIScrollView!
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
configureScrollView()
configurePageControl()
}
func configureScrollView() {
// Enable paging.
scrollView.pagingEnabled = true
// Set the following flag values.
scrollView.showsHorizontalScrollIndicator = false
scrollView.showsVerticalScrollIndicator = false
scrollView.scrollsToTop = false
// Set the scrollview content size.
scrollView.contentSize = CGSizeMake(scrollView.frame.size.width * CGFloat(totalPages), scrollView.frame.size.height)
// Set self as the delegate of the scrollview.
scrollView.delegate = self
// Load the TestView view from the TestView.xib file and configure it properly.
for i in 0 ..< totalPages {
// Load the TestView view.
let testView = NSBundle.mainBundle().loadNibNamed("TestView", owner: self, options: nil)[0] as! UIView
// Set its frame and the background color.
testView.frame = CGRectMake(CGFloat(i) * scrollView.frame.size.width, scrollView.frame.origin.y, scrollView.frame.size.width, scrollView.frame.size.height)
testView.backgroundColor = sampleBGColors[i]
// Set the proper message to the test view's label.
let label = testView.viewWithTag(1) as! UILabel
label.text = "Page #\(i + 1)"
// Add the test view as a subview to the scrollview.
scrollView.addSubview(testView)
}
}
func configurePageControl() {
// Set the total pages to the page control.
pageControl.numberOfPages = totalPages
// Set the initial page.
pageControl.currentPage = 0
}
// MARK: UIScrollViewDelegate method implementation
func scrollViewDidScroll(scrollView: UIScrollView) {
// Calculate the new page index depending on the content offset.
let currentPage = floor(scrollView.contentOffset.x / UIScreen.mainScreen().bounds.size.width);
// Set the new page index to the page control.
pageControl.currentPage = Int(currentPage)
}
// MARK: IBAction method implementation
#IBAction func changePage(sender: AnyObject) {
// Calculate the frame that should scroll to based on the page control current page.
var newFrame = scrollView.frame
newFrame.origin.x = newFrame.size.width * CGFloat(pageControl.currentPage)
scrollView.scrollRectToVisible(newFrame, animated: true)
}
Please help me! Thank you.
Sorry for my English is bad.
Change the pageControl.currentPage in UIScrollViewDelegate's implemention scrollViewDidEndScrollingAnimation and scrollViewDidEndDecelerating, and I improved the calculation with scrollView's width, not screen's width:
// MARK: UIScrollViewDelegate method implementation
func scrollViewDidEndScrollingAnimation(scrollView: UIScrollView) {
// Calculate the new page index depending on the content offset.
let currentPage = floor(scrollView.contentOffset.x / scrollView.bounds.size.width)
// Set the new page index to the page control.
pageControl.currentPage = Int(currentPage)
}
func scrollViewDidEndDecelerating(scrollView: UIScrollView){
scrollViewDidEndScrollingAnimation(scrollView)
}
I'm trying to display multiple images using a UIScrollView and a page control, think screenshots of apps on the App Store. Yet for some reason, my UIScrollView is not scrolling. I checked, and UISCrollView's contentSize's width is larger than the UIScrollView's width. It might also be worth noting that I put the page control in the UIScrollView, so that it displays on top of the pictures. My code is as follows:
import UIKit
class ItemDetailViewController: UIViewController, UIScrollViewDelegate {
#IBOutlet weak var scrollView: UIScrollView!
#IBOutlet weak var pageControl: UIPageControl!
var itemSelected: Item!
var pageViews: [UIImageView?] = []
var pageCount: Int!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
pageCount = itemSelected.images.count
pageControl.currentPage = 0
pageControl.numberOfPages = pageCount
for _ in 0..<pageCount {
pageViews.append(nil)
}
scrollView.frame.size = CGSizeMake(view.frame.width, view.frame.height/2.0)
let pageSize = scrollView.frame.size
scrollView.contentSize = CGSizeMake(pageSize.width * CGFloat(pageCount), pageSize.height)
loadVisiblePages()
scrollView.delegate = self
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
tabBarController?.tabBar.hidden = true
}
override func viewWillDisappear(animated: Bool) {
super.viewDidDisappear(animated)
tabBarController?.tabBar.hidden = false
}
// MARK: - Helper Functions
func loadPage(page: Int){
if page < 0 || page >= pageCount {
// page outside of range, do nothing
return
}
if let pageView = pageViews[page] {
// page already loaded, do nothing
return
} else {
var frame = scrollView.bounds
frame.origin.x = frame.size.width * CGFloat(page)
frame.origin.y = 0.0
let newPageView = UIImageView(image: itemSelected.images[page])
newPageView.contentMode = .ScaleToFill
newPageView.frame = frame
scrollView.addSubview(newPageView)
pageViews[page] = newPageView
}
}
func purgePage(page: Int){
if page < 0 || page >= pageCount {
// page outside of range, do nothing
return
}
if let pageView = pageViews[page]{
pageView.removeFromSuperview()
pageViews[page] = nil
}
}
func loadVisiblePages(){
let pageWidth = scrollView.frame.size.width
let page = Int(floor(scrollView.contentOffset.x * 2.0 + pageWidth)/(2.0 * pageWidth))
pageControl.currentPage = page
let firstPage = page - 1
let lastPage = page + 1
for var index = 0; index < firstPage; index++ {
purgePage(index)
}
for index in firstPage ... lastPage {
loadPage(index)
}
for var index = lastPage + 1; index < itemSelected.images.count; index++ {
purgePage(index)
}
}
// MARK: - Scroll View Delegate Methods
func scrollViewDidScroll(scrollView: UIScrollView) {
loadVisiblePages()
}
}
What could be causing the issue?
Better use UIPageViewController is u want only show images.
u can see tutorial how do it there there