Manipulate constraint to move a view to the bottom of the screen - ios

I have a collection view that is aligned horizontally and vertically in the container. When a user selects the cell I want that collection view to move to the bottom of the screen, and then I want a UIView to appear right above the collection view.
Below is how my collection view is constrained.
So the way I went about this was making the bottom space constraint an outlet in my code file.
#IBOutlet var collectionViewBottomConstraint: NSLayoutConstraint!
Then when a user tapped on the cell, i ran this function here
func showWatchView(selectedPath: Int) {
UIView.animate(withDuration: 0.3, animations: {
self.collectionViewBottomConstraint.constant = 0
})
clipsCollectionView.layoutIfNeeded()
}
However, when I tried this it moved the collection view to the top of the screen instead of the bottom of the screen, and it didn't even animate it, it just went up there.

The constant needs to be set to 0
class ViewController: UIViewController, UICollectionViewDataSource, UICollectionViewDelegate {
#IBOutlet weak var collectionViewOutlet: UICollectionView!
#IBOutlet weak var bottomContraint: NSLayoutConstraint!
override func viewDidLoad() {
super.viewDidLoad()
collectionViewOutlet.delegate = self
collectionViewOutlet.dataSource = self
}
func numberOfSections(in collectionView: UICollectionView) -> Int {
return 1
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 1
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath)
return cell
}
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
showWatchView(selectedPath: indexPath.item)
}
func showWatchView(selectedPath: Int) {
UIView.animate(withDuration: 0.3, delay: 0, options: .curveEaseOut, animations: {
self.bottomContraint.constant = 0
self.view.layoutIfNeeded()
}, completion: { (completed) in
// do something
})
}
}
Since you want the collectionView to be at the bottom and self.view.layoutIfNeeded() needs to be called inside the animation with a duration of your choosing, 0.3 is ok for starters.

Related

Crousel not working properly and showing Blank screen

In image we cleraly see the crousel showing blank image but I want crousel that shows images that bot happening the array of the image are on next slide/page I have added crousel with UICollectionview give the UIcollectionview image size 300/200
it doesn't scroll manually and if we scroll it forcefully it went to first screen and show Blank imageview.
NOT showing the crousel Which contain advertisement
not Working crousel and UICollectionView
import UIKit
class AdCollectionView: UICollectionViewCell {
#IBOutlet weak var adImage: UIImageView!
}
import UIKit
class Marketplace: UIViewController {
#IBOutlet weak var adCollectionView: UICollectionView!
var Images=["scan","stethscope","pulse","Reorder","specialist","trolley"]
var timer:Timer?
var currentIndex = 0
override func viewDidLoad() {
super.viewDidLoad()
timer = Timer.scheduledTimer(timeInterval:2.0, target: self, selector: #selector(slideToNext), userInfo: nil, repeats: true)
}
#objc func slideToNext(){
if currentIndex < Images.count - 1
{
currentIndex+=currentIndex
adCollectionView.scrollToItem(at: IndexPath(item: currentIndex, section: 0), at: .right, animated: true)
}
else{
currentIndex = 0
}
}
}
extension Marketplace:UICollectionViewDelegate,UICollectionViewDataSource,UICollectionViewDelegateFlowLayout
{
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return Images.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell=adCollectionView.dequeueReusableCell(withReuseIdentifier:"AdCell", for: indexPath) as! AdCollectionView
cell.adImage.image=UIImage(named: Images[indexPath.row])
return cell
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: adCollectionView.frame.width, height: adCollectionView.frame.height)
}
}

Start playling Lottie once collection view is changed

I have problem with lottie animations, I have some kind of onboarding on my app and what I would like to achive is to everytime view in collectionview is changed, to start my animation, I have 4 pages and 4 different lottie animations. Currently if I call animation.play() function, once app is started, all of my animations are played at the same time, so once I get to my last page, animation is over. And I want my lottie to be played only once, when view is shown.
This is my cell
class IntroductionCollectionViewCell: UICollectionViewCell {
#IBOutlet var title: UILabel!
#IBOutlet var subtitleDescription: UILabel!
#IBOutlet var animationView: AnimationView!
override func awakeFromNib() {
super.awakeFromNib()
}
public func configure(with data: IntroInformations) {
let animation = Animation.named(data.animationName)
title.text = data.title
subtitleDescription.text = data.description
animationView.animation = animation
}
static func nib() -> UINib {
return UINib(nibName: "IntroductionCollectionViewCell", bundle: nil)
}
}
This is how my collection view is set up
extension IntroductionViewController {
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat {
return 0
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
pages.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "IntroductionCollectionViewCell", for: indexPath) as! IntroductionCollectionViewCell
cell.configure(with: pages[indexPath.row])
cell.animationView.loopMode = .loop
cell.animationView.play()
return cell
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: view.frame.width, height: view.frame.height)
}
func scrollViewDidScroll(_ scrollView: UIScrollView) {
let scrollPos = scrollView.contentOffset.x / view.frame.width
self.pageControl.currentPage = Int(floorf(Float(scrollPos)))
let n = pageControl.numberOfPages
if self.pageControl.currentPage == n - 1 {
continueButton.isHidden = false
} else {
continueButton.isHidden = true
}
}
}
Thanks in advance!!
You can use the collectionViewDelegate willDisplay and didEndDisplaying methods to start/stop the animations. And not when configuring the cell. - https://developer.apple.com/documentation/uikit/uicollectionviewdelegate
If you want the animation to run only once dont use the loop option.
if let cell = cell as? IntroductionCollectionViewCell {
cell.animationView.loopMode = .playOnce
cell.animationView.play()
}
this is the answer, I need to check is cell once is loaded my cell where lottie animation is

Constraints reactivate when contextMenu appears

I am using UIContextMenuConfiguration for actions on a collection view cell. Everything works exactly as expected, however if my cell has (de)activated constraints from nib, it refreshes upon long press.
To demonstrate the problem, I have created a new project, with a collectionView in the storyboard. A custom cell in the collection view has a label with two constraints, a constraint pinning it to the bottom of the cell (initially active), and the other aligns it in the center (initially disabled).
Here's my code,
import UIKit
class ViewController: UIViewController, UICollectionViewDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout {
func numberOfSections(in collectionView: UICollectionView) -> Int {
return 1
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 10
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as! CollectionViewCell
cell.configure()
return cell
}
func collectionView(_ collectionView: UICollectionView, contextMenuConfigurationForItemAt indexPath: IndexPath, point: CGPoint) -> UIContextMenuConfiguration? {
return UIContextMenuConfiguration.init(identifier: nil, previewProvider: nil) { (array) -> UIMenu? in
return UIMenu(title: "Hello", image: nil, identifier: nil, options: .destructive, children: [UIAction(title: "Share", image: UIImage(systemName: "tray.and.arrow.up"), identifier: nil) { _ in
print("HelloWorld!")
}])
}
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: 265, height: 128)
}
}
class CollectionViewCell: UICollectionViewCell {
#IBOutlet weak var bottomConstraint: NSLayoutConstraint!
#IBOutlet weak var centerConstraint: NSLayoutConstraint!
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
}
func configure() {
bottomConstraint.isActive = false
centerConstraint.isActive = true
}
}
The issue appears to be directly related to:
two constraints created in Storyboard which would conflict with each other
setting one constraint to Not Installed
de-activating the installed constraint and activating the not-installed constraint
When initializing UIContextMenuConfiguration with previewProvider: nil - which tells UIKit to auto-generate a preview view - the "installed" states of the constraints are reset.
One way to get around it:
Instead of marking the Center constraint "not installed" give it a Priority of 998, and give the Bottom constraint a Priority of 999 (that will prevent IB from complaining).
You can then keep your configure() func as:
func configure() {
bottomConstraint.isActive = false
centerConstraint.isActive = true
}
and the label's centerY constraint will remain active in both the cell and in the context menu's preview.
However, if you WANT the previewView to look different than the cell, your best bet (and probably a better approach to begin with) is to use a custom previewProvider.

Scrolling issue with UITableViewController hosted in UICollectionViewCell

I have the following setup in my app:
UITabBarController
UINavigationController
UIViewController
The UIViewController has a UICollectionView with horizontal scrolling.
In the cells, I want to "host" a view from another ViewController. This works pretty well, but I have scrolling issues. The first UICollectionViewCell hosts a view that comes from a UITableViewController. I can scroll the UITableViewController but it does not really scroll to the end - it seems like the UITableViewController starts to bounce way too early.
When I used the UITableViewController as the Root View Controller, everything worked fine, so I don't think there is something wrong with this ViewController.
The height of the CollectionView is pretty small, I just wanted to show the "bouncing" behaviour.
Here is the code for the collectionView:
import Foundation
import UIKit
class FeedSplitViewController : UIViewController, Controllable
{
#IBOutlet weak var menuBar: MenuBar!
#IBOutlet weak var collectionView: UICollectionView!
private var currentIndex = 0
private var dragStart: CGFloat = 0.0
private var feedActivities: FeedViewController!
var controller: Controller!
override func viewDidLoad()
{
super.viewDidLoad()
self.initControls()
self.initMenuBar()
self.initCollectionView()
self.initActivitiesViewController()
}
fileprivate func initActivitiesViewController()
{
self.feedActivities = UIStoryboard.instantiate("Main", "feedActivities")
self.feedActivities.controller = self.controller
}
fileprivate func initControls()
{
self.navigationController?.navigationBar.setValue(false, forKey: "hidesShadow")
}
fileprivate func initMenuBar()
{
self.menuBar.showLine = true
self.menuBar.enlargeIndicator = true
self.menuBar.texts = [Resources.get("FEED_ACTIVITIES"), Resources.get("DASHBOARD")]
self.menuBar.selectionChanged =
{
index in
self.collectionView.scrollToItem(at: IndexPath(item: index, section: 0), at: UICollectionView.ScrollPosition.right, animated: true)
}
}
fileprivate func initCollectionView()
{
self.collectionView.register(UICollectionViewCell.self, forCellWithReuseIdentifier: "cell")
let menuBarFrame = self.menuBar.frame.origin
let collectionView = self.collectionView.frame.origin
Swift.print(menuBarFrame)
Swift.print(collectionView)
}
}
extension FeedSplitViewController : UICollectionViewDelegate, UICollectionViewDelegateFlowLayout, UICollectionViewDataSource
{
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int
{
return 2
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell
{
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath)
if indexPath.item == 0, let feedActivities = self.feedActivities
{
cell.contentView.addSubview(feedActivities.view)
}
return cell
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat
{
return 0
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize
{
return CGSize(width: self.view.bounds.width, height: self.view.bounds.height)
}
func scrollViewWillBeginDragging(_ scrollView: UIScrollView)
{
self.dragStart = scrollView.contentOffset.x
}
func scrollViewDidScroll(_ scrollView: UIScrollView)
{
let oldIndex = self.currentIndex
let page = scrollView.contentOffset.x / scrollView.frame.size.width
let currentPage = Int(round(page))
if oldIndex != currentPage
{
if Settings.useHapticFeedback
{
Utilities.haptic(.medium)
}
self.menuBar.selectedIndex = currentPage
}
self.currentIndex = currentPage
}
}
I have attached a small video: https://imgur.com/a/pj7l3Hd
I solved it by doing the following:
I no longer host the view of an ViewController directly in the
Every UICollectionView cell hosts an UITableView.
The UITableViewCell contains the data model that was previously implemented in the ViewController. The logic is still outside of the UITableViewCell.

Expandable UICollectionViewCell

I'm trying to program an expandable UICollectionViewCell.
If the user presses the topbutton, the cell should expand to a different height and reveal the underlying content.
My first implementation features a custom UICollectionViewCell, which can be expanded by clicking the topbutton, but the collectionview does not expand too. Atm. the cell is the last one in the collectionview and just by swiping down enough, you can see the expanded content. If you stop touching, the scrollview jumps up and nothing of the expanded cell could be seen.
Here's my code:
import Foundation
import UIKit
class ExpandableCell: UICollectionViewCell {
#IBOutlet weak var topButton: IconButton!
public var expanded : Bool = false
private var expandedHeight : CGFloat = 200
private var notExpandedHeight : CGFloat = 50
#IBAction func topButtonTouched(_ sender: AnyObject) {
if (expanded == true) {
self.deExpand()
} else {
self.expand()
}
}
public func expand() {
UIView.animate(withDuration: 0.8, delay: 0.0, usingSpringWithDamping: 0.9, initialSpringVelocity: 0.9, options: UIViewAnimationOptions.curveEaseInOut, animations: {
self.frame = CGRect(x: self.frame.origin.x, y: self.frame.origin.y, width: self.frame.size.width, height: self.expandedHeight)
}, completion: { success in
self.expanded = true
})
}
public func deExpand() {
UIView.animate(withDuration: 0.8, delay: 0.0, usingSpringWithDamping: 0.9, initialSpringVelocity: 0.9, options: UIViewAnimationOptions.curveEaseInOut, animations: {
self.frame = CGRect(x: self.frame.origin.x, y: self.frame.origin.y, width: self.frame.size.width, height: self.notExpandedHeight)
}, completion: { success in
self.expanded = false
})
}
}
I also have problems implementing the following method correctly, because I haven't got a direct reference to the cell:
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize
Basicly everything should be animated.
The screenshot shows an expanded and not expanded cell. Hope that helps!
Thanks!
If you have more than cell that needs to be expanded to different height when a button is touched inside the collection view cell, then, here is the code.
I have used the delegate pattern to let the controller know, button of which cell was touched using the indexPath.
You need to pass the indexpath of the cell to the cell when creating the cell.
when the button is touched the cell calls the delegate(ViewController), which updates the isExpandedArray accordingly and reloads the particular cell.
CollectionViewCell
protocol ExpandedCellDelegate:NSObjectProtocol{
func topButtonTouched(indexPath:IndexPath)
}
class ExpandableCell: UICollectionViewCell {
#IBOutlet weak var topButton: UIButton!
weak var delegate:ExpandedCellDelegate?
public var indexPath:IndexPath!
#IBAction func topButtonTouched(_ sender: UIButton) {
if let delegate = self.delegate{
delegate.topButtonTouched(indexPath: indexPath)
}
}
}
View Controller Class
class ViewController: UIViewController {
#IBOutlet weak var collectionView: UICollectionView!
var expandedCellIdentifier = "ExpandableCell"
var cellWidth:CGFloat{
return collectionView.frame.size.width
}
var expandedHeight : CGFloat = 200
var notExpandedHeight : CGFloat = 50
var dataSource = ["data0","data1","data2","data3","data4"]
var isExpanded = [Bool]()
override func viewDidLoad() {
super.viewDidLoad()
isExpanded = Array(repeating: false, count: dataSource.count)
}
}
extension ViewController:UICollectionViewDataSource{
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return dataSource.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: expandedCellIdentifier, for: indexPath) as! ExpandableCell
cell.indexPath = indexPath
cell.delegate = self
//configure Cell
return cell
}
}
extension ViewController:UICollectionViewDelegateFlowLayout{
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
if isExpanded[indexPath.row] == true{
return CGSize(width: cellWidth, height: expandedHeight)
}else{
return CGSize(width: cellWidth, height: notExpandedHeight)
}
}
}
extension ViewController:ExpandedCellDelegate{
func topButtonTouched(indexPath: IndexPath) {
isExpanded[indexPath.row] = !isExpanded[indexPath.row]
UIView.animate(withDuration: 0.8, delay: 0.0, usingSpringWithDamping: 0.9, initialSpringVelocity: 0.9, options: UIViewAnimationOptions.curveEaseInOut, animations: {
self.collectionView.reloadItems(at: [indexPath])
}, completion: { success in
print("success")
})
}
}
Don't change the cell frame directly. Implement func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize and handle heights there.
Lets say you have one cell in your collectionView and a top button above it:
let expanded = false
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize{
if expanded{
// what is the size of the expanded cell?
return collectionView.frame.size
}else{
// what is the size of the collapsed cell?
return CGSizeZero
}
}
#IBAction func didTouchUpInsideTopButton(sender: AnyObject){
// top button tap handler
self.expanded = !self.expanded
// this call will reload the cell and make it redraw (animated) Since the expanded flag changed, the sizeForItemAt call above will return a different size and animate the size change
self.collectionView.reloadItemsAtIndexPaths([NSIndexPath(forRow: 0, inSection: 0)])
}
You can use header to show non-expand cell. And when you two a button or something in it , then you can insert other part (expanding part ) as a row in that section which the header belongs to.
Benefits - you can use many provided animations when the expanding happen.

Resources