I'm developing app which contains 3 UICollectionViews in one ViewController.
I can know if UICollectionView is scrolled or not with this code
func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) {
let isScrolling: Bool = colView.isDragging || colView.isDecelerating
}
Specifically I want to know if which UICollectionView did scroll in scrollViewDidEndDragging.
But the problem is that I can't know if which collectionview is scrolled before.
Like I want to know if colview2 is scrolling or not.
Is there anybody who knows this solution? I searched solution on Google and Stackoverflow but I don't think there's such a solution for this problem.
Help will be much appreciated.
You can Do it by set tag to CollectionView And Scroll View Delegate method scrollViewDidEndDecelerating Here is the Code :
First Set tag top your CollectionView in your ViewDidLoad method :
firstCollectionView.tag = 1
secondCollectionView.tag = 2
thirdCollectionView.tag = 3
2.Create three Property Observer variable like this upside of your viewDidload:
var whichCollectionViewScrolled = "" {
willSet{
print(newValue)
}
}
var isFirstCollectionViewScrolled = false {
willSet{
print("First CollectionView Scrolled : \(newValue)")
}
}
var isSecondCollectionViewScrolled = false {
willSet{
print("Second CollectionView Scrolled : \(newValue)")
}
}
var isthirdCollectionViewScrolled = false {
willSet{
print("Third CollectionView Scrolled : \(newValue)")
}
}
1.lastly inside of your scrollViewDelegate method cast your scrollview instance and check the tag value like this :
extension ViewController: UIScrollViewDelegate {
func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
if let collectionView = scrollView as? UICollectionView {
switch collectionView.tag {
case 1:
whichCollectionViewScrolled = "First"
isFirstCollectionViewScrolled = true
isSecondCollectionViewScrolled = false
isthirdCollectionViewScrolled = false
case 2:
whichCollectionViewScrolled = "second"
isFirstCollectionViewScrolled = false
isSecondCollectionViewScrolled = true
isthirdCollectionViewScrolled = false
case 3:
whichCollectionViewScrolled = "Third"
isFirstCollectionViewScrolled = false
isSecondCollectionViewScrolled = false
isthirdCollectionViewScrolled = true
default:
whichCollectionViewScrolled = "unknown"
}
} else{
print("cant cast")
}
}
Hope it will help You .
Scroll view is the superClass of UICollectionView. Just check that scroll view and collection view you are storing are the same instance.
if collectionView === scrollView {
}
Like this.
Related
I have implemented programmatically this:
List of tableView cells.
Each tableViewCell contains a
collectionView and below it a Button.
What's happening now:
Focus engine is on a collection View
User scrolls down
Focus engine goes to next collection view unless I'm on the last item of the focused collection view, then it goes to VoirTout button.
What I want is:
Focus engine is on a collection View
User scrolls down
Focus engine goes to Voir Tout Button
I have seen a couple of answers that make use of preferredFocusEnvironments like this:
// Trying to force focus on button
var voirToutButton: CustomButton? = CustomButton(color: .red, titleString: "");
override var preferredFocusEnvironments : [UIFocusEnvironment] {
return [voirToutButton!]
}
And then calling these inside viewDidLoad:
// TRYING TO FORCE FOCUS ON VOIR TOUT BUTTON
setNeedsFocusUpdate()
updateFocusIfNeeded()
But, in my case that had no effect.
I solved it like this. I'm not sure this is the most optimal way but it works:
class TableViewCell: UITableViewCell {
var voirToutButton: CustomButton? = CustomButton(color: .red, titleString: "");
var shouldRefocusToVoirToutButton:Bool = false
override var preferredFocusEnvironments : [UIFocusEnvironment] {
return [voirToutButton!]
}
override func shouldUpdateFocus(in context: UIFocusUpdateContext) -> Bool {
if (self.shouldRefocusToVoirToutButton){
// Resetting this to false in order to avoid the focus getting stuck on voirToutButton
self.shouldRefocusToVoirToutButton = false
self.setNeedsFocusUpdate()
self.updateFocusIfNeeded()
}
return true
}
}
extension TableViewCell: UICollectionViewDelegate, UICollectionViewDataSource {
func collectionView(_ collectionView: UICollectionView, shouldUpdateFocusIn context: UICollectionViewFocusUpdateContext) -> Bool {
if let previouslyFocusedItemParent = context.previouslyFocusedItem?.parentFocusEnvironment as? UICollectionView, let nextFocusedItemParent = context.nextFocusedItem?.parentFocusEnvironment as? UICollectionView {
let isPreviousFocusInCurrentCollectionView = previouslyFocusedItemParent == self.moviesCollectionView
let isNextFocusInCurrentCollectionView = nextFocusedItemParent == self.moviesCollectionView
let isFocusLeavingCollectionView = !(isPreviousFocusInCurrentCollectionView == isNextFocusInCurrentCollectionView)
let isFocusMovingDown:Bool = !(context.focusHeading == .up)
if (isFocusLeavingCollectionView && isFocusMovingDown) {
self.shouldRefocusToVoirToutButton = true
}
return true
}
// I remove this else it says that I need to return a bool (!(
else {
print("If I remove this else it says that I need to return a bool (!(")
return true
}
}
}
In my ViewController, I have 12 custom UIViews(named CardView).
I am trying to iterate through the subviews of the ViewController programmatically to find the custom view(CardView) and do some configuration. Following is my code to count the number of CardViews.
private func cardCount()->Int{
var count = 0
for subview in self.view.subviews{
if subview is CardView{
count = count + 1
}
}
return count
}
However, it is returning me '0' and the reason being my views are embedded inside UIStackViews. I have 3 horizontally aligned stack views inside a vertically aligned one like-
How can I get my CardViews programmatically. Any help would be appreciated.
You can do a few flat maps to flatten your view structure first, then count them:
private func cardCount()->Int{
var count = 0
for subview in self.view.subviews.flatMap { $0.subviews }.flatMap { $0.subviews }{
if subview is CardView{
count = count + 1
}
}
return count
}
But I feel like you are doing things the wrong way around. The number of cards sounds like something in your model. You should have a variable called cardCount and the cards on the screen are supposed to change according to that variable, not the other way around.
You create IBOutlets to each of the horizontal stack views. Then loop through the subviews in the stackviews.
#IBOutlet weak var StackView1: UIStackView!
#IBOutlet weak var StackView2: UIStackView!
#IBOutlet weak var StackView3: UIStackView!
for customView in StackView1.arrangedSubviews
{
// Do task
}
Try this
private func cardCount() -> Int
{
var count = 0
for subview in self.view.subviews
{
if let stackView: UIStackView = subview as? UIStackView
{
let arrangedViews = stackView.arrangedSubviews
for cardView in arrangedViews
{
if cardView is CardView
{
count = count + 1
}
}
}
}
return count
}
From the pictorial diagram you shared, it seems the StackView is the first child. Whenever you get the subviews, only the first child views are returned. So self.view.subviews would result in only one UIStackView. Give a tag to each stack view. Then, in code :
private func cardCount()->Int{
var count = 0
for subview in self.view.subviews{
if subview.tag == 10 { // parent stack view
for subviewStack in subview { // Get subviews of parent stackview
if subviewStack.tag == 11 { // First stack view child
for subViewSubStack in subviewStack.subviews { // Get card views in this stack view
// Apply your count logic
}
}
}
}
}
return count
}
I have written only the first condition. You might add others.
Having said this, I won't say this is the most optimum solution. This can and should be refactored.
Create an extension method like this. It will count all CardView instances in the view controller.
extension UIView {
func cardCount() -> Int {
switch self {
case let self as CardView:
return 1
case let self as UIStackView:
return self.arrangedSubviews.reduce(0, { $0 + $1.cardCount() })
default:
return self.subviews.reduce(0, { $0 + $1.cardCount() })
}
}
}
and call this method in viewDidLoad
class ViewControllerd: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
print(self.view.cardCount())
}
}
Sample project can be found at https://github.com/SRowley90/LargeTitleIssueTestiOS
I am trying to position a segmented control below the Large title in an iOS app. I have a UIToolbar which contains the segmented control inside.
When scrolling up the title and toolbar behave as expected.
When scrolling down the navigation bar is correct, but it doesn't push the UITabBar or the UITableView down, meaning the title goes above the segmented control as can be seen in the images below.
I'm pretty sure it's something to do with the constraints I have set, but I can't figure out what.
The TabBar is fixed to the top, left and right.
The TableView is fixed to the bottom, left and right.
The tableView is fixed vertically to the TabBar
I have the position UITabBarDelegate method set:
func position(for bar: UIBarPositioning) -> UIBarPosition {
return .topAttached
}
Take the delegation of the tableView somewhere:
tableView.delegate = self
Override the scrollViewDidScroll and update toolbar position appearance (since the real position should not change according to have that nice bounce effect.
extension ViewController: UIScrollViewDelegate {
override func scrollViewDidScroll(_ scrollView: UIScrollView) {
var verticalOffset = scrollView.contentOffset.y + defaultNavigationBarHeight
if scrollView.refreshControl?.isRefreshing ?? false {
verticalOffset += 60 // After is refreshing changes its value the toolbar goes 60 points down
print(toolbar.frame.origin.y)
}
if verticalOffset >= 0 {
toolbar.transform = .identity
} else {
toolbar.transform = CGAffineTransform(translationX: 0, y: -verticalOffset)
}
}
}
You can use the following check before applying transformation to make it more reliable and natural to default iOS style:
if #available(iOS 11.0, *) {
guard let navigationController = navigationController else { return }
guard navigationController.navigationBar.prefersLargeTitles else { return }
guard navigationController.navigationItem.largeTitleDisplayMode != .never else { return }
}
Using UIScrollViewDelegate didn't work well with CollectionView and toolbar for me. So, I did:
final class CollectionViewController: UICollectionViewController {
private var observesBag: [NSKeyValueObservation] = []
private let toolbar = UIToolbar()
override func viewDidLoad() {
super.viewDidLoad()
let statusBarHeight = UIApplication.shared.statusBarFrame.height
let navigationBarHeight = navigationController?.navigationBar.frame.height ?? 0
let defaultNavigationBarHeight = statusBarHeight + navigationBarHeight
let observation = navigationController!
.navigationBar
.observe(\.center, options: NSKeyValueObservingOptions.new) { [weak self] navBar, _ in
guard let self = self else { return }
let newNavigatonBarHeight = navBar.frame.height + statusBarHeight
let yTranslantion = newNavigatonBarHeight - defaultNavigationBarHeight
if yTranslantion > 0 {
self.toolbar.transform = CGAffineTransform(
translationX: 0,
y: yTranslantion
)
} else {
self.toolbar.transform = .identity
}
}
observesBag.append(observation)
}
}
Observe the "center" of the navigationBar for changes and then translate the toolbar in the y-axis.
Even though it worked fine when I tried to use this solution with UIRefreshControl and Large Titles it didn't work well.
I set up the refresh control like:
private func setupRefreshControl() {
let refreshControl = UIRefreshControl()
self.webView.scrollView.refreshControl = refreshControl
}
the height of the UINavigationBar is changed after the complete refresh triggers.
I'm currently battling with the keyboard covering some textField issue on my Swift based iPhone app.
This image shows the primary problem (top three images show the problem, bottom three are what it should be doing):
When the textField in the tableViewCell is edited I want to move the whole tableview and navigation bar up. I can do this with the code below (snippets taken from the viewController):
var tableViewShiftedUp = false
...
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("cell", forIndexPath: indexPath) as! QuantityTableViewCell
let componentLocationQuantity = tableViewData[indexPath.row]
if let locationString = componentLocationQuantity.location.valueForKey("location_name") as? String {
cell.setCellContents(locationString, quantity: componentLocationQuantity.quantity.floatValue)
cell.quantityLabel.delegate = self
}
//get stock level related to current build rate and lead time (good - green, warning - orange, urgent - red)
let status = model.determineStockLevelStatus(component)
cell.setQuantityLabelBackgroundStatusColor(status)
if editingMode == true {
cell.makeQuantityEditable()
}
//add control event to detect text change
cell.quantityLabel.addTarget(self, action: #selector(quantityCellTextChanged(_:)), forControlEvents: UIControlEvents.EditingChanged)
cell.quantityLabel.addTarget(self, action: #selector(quantityCellSelected(_:)), forControlEvents: UIControlEvents.EditingDidBegin)
return cell
}
...
//MARK: - UITextfield delegate
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
self.view.endEditing(true)
if tableViewShiftedUp == true {
moveTable()
}
}
func textFieldShouldReturn(textField: UITextField) -> Bool {
textField.resignFirstResponder()
return true
}
func quantityCellSelected(textField: UITextField){
if tableViewShiftedUp == false {
moveTable()
}
//check table was moved
print(tableView.frame.origin.y)
}
func hideKeyboard(){
self.view.endEditing(true)
if tableViewShiftedUp == true {
moveTable()
}
}
func moveTable(){
if tableViewShiftedUp == true {
animateTableViewMoving(false, moveValue: 250)
animateNavigationBarMoving(false, moveValue: 250)
tableViewShiftedUp = false
} else {
animateTableViewMoving(true, moveValue: 250)
animateNavigationBarMoving(true, moveValue: 250)
tableViewShiftedUp = true
}
}
// moving the tableView
func animateTableViewMoving (up:Bool, moveValue :CGFloat){
let movementDuration:NSTimeInterval = 0.0
let movement:CGFloat = ( up ? -moveValue : moveValue)
UIView.beginAnimations( "animateView", context: nil)
UIView.setAnimationBeginsFromCurrentState(true)
UIView.setAnimationDuration(movementDuration )
self.tableView.frame = CGRectOffset(self.tableView.frame, 0, movement)
UIView.commitAnimations()
}
// moving the navigationBar
func animateNavigationBarMoving (up:Bool, moveValue :CGFloat){
let movementDuration:NSTimeInterval = 0.0
let movement:CGFloat = ( up ? -moveValue : moveValue)
UIView.beginAnimations( "animateView", context: nil)
UIView.setAnimationBeginsFromCurrentState(true)
UIView.setAnimationDuration(movementDuration )
self.midNavigationBar.frame = CGRectOffset(self.midNavigationBar.frame, 0, movement)
UIView.commitAnimations()
}
And it works fine when moving directly to the textField in the tableView. However, when I am already editing a textField outside the tableView the shift up doesn't happen and so the shifting toggle gets out of sync.
I've printed the tableView frame origin:
//check table was moved
print(tableView.frame.origin.y)
so I can see what the tableView is set to and on that first move from textField outside the tableView to textField inside the tableView, this property is what I would expect it to be 134, however it's still at 384 on the screen which it prints the next time it's called.
The problem doesn't occur when moving within cells in the tableView.
It feels like I've got some kind of race condition problem or I'm missing some delegate method being called that is throwing everything off when transitioning from one cell to the next.
Help would be great.
Check out this library: https://github.com/michaeltyson/TPKeyboardAvoiding.
Designed so that it gets the keyboard out of the way of text fields.
When you use autolayout you cannot resize frame directly like you doing, you must work with constraints (you can create IBOutlets for constraints created in IB or add new ones, then send layoutIfNeeded to your view from within an animation block).
You can follow official Apple instructions:
About Auto Layout and Layout Constraints
Animating Layer Content
I'm trying to find my UILabels in my superview of my UIViewControllers.
This is my code:
func watch(startTime:String, endTime:String) {
if superview == nil {println("NightWatcher: No viewcontroller specified");return}
listSubviewsOfView(self.superview!)
}
func listSubviewsOfView(view: UIView) {
var subviews = view.subviews
if (subviews.count == 0) { return }
view.backgroundColor = UIColor.blackColor()
for subview in subviews {
if subview.isKindOfClass(UILabel) {
// do something with label..
}
self.listSubviewsOfView(subview as UIView)
}
}
This is how it is recommended to in Objective-C, but in Swift I get nothing but UIViews and CALayer. I definitely have UILabels in the view that is supplied to this method. What am I missing?
The call in my UIViewController:
NightWatcher(view: self.view).watch("21:00", endTime: "08:30") // still working on
Here's a version that will return an Array of all the UILabel views in whatever view you pass in:
func getLabelsInView(view: UIView) -> [UILabel] {
var results = [UILabel]()
for subview in view.subviews as [UIView] {
if let labelView = subview as? UILabel {
results += [labelView]
} else {
results += getLabelsInView(view: subview)
}
}
return results
}
Then you can iterate over them to do whatever you'd like:
override func viewDidLoad() {
super.viewDidLoad()
let labels = getLabelsInView(self.view)
for label in labels {
println(label.text)
}
}
Using functional programming concepts you can achieve this much easier.
let labels = self.view.subviews.flatMap { $0 as? UILabel }
for label in labels {
//Do something with label
}
Swift 4
Adepting mKane's answer you can use this code:
let labels = self.view.subviews.compactMap { $0 as? UILabel }
for label in labels {
// do whatever
}
You could set a tag to your UILabel in the Storyboard or programmatically using:
myLabel.tag = 1234
Then, to find it use:
let myLabel = view.viewWithTag(1234)