I am trying to create a custom transition, similar to the one that is on the App Store. I used these tutorials:
https://eric-dockery283.medium.com/custom-view-transitions-like-the-new-app-store-a2a1181229b6
https://www.raywenderlich.com/2925473-ios-animation-tutorial-custom-view-controller-presentation-transitions
I have also been experimenting with using the load view to have the root view to create the view, similar to what is used in the answer used here: does loadView get called even if we don't override it in viewcontroller like the other ViewController lifecycle methods?
now what I am doing is that I am using a collectionview, which I want to keep, and when an item is pressed it animate transitions to a new view controller with a larger image, simple.
the problem is that when I combine it with using the loadView with view = rootView() it did not work in some scenarios.
For example:
when I use the modalPresentationStyle = .fullScreen, when I press an item in the collection view the screen just goes black, and then when I press the view hierarchy it shows this:
when I remove the modalPresentationStyle = .fullScreen it does animate but it is the popover transition, which I do not want.
What I want is to have a full screen transition that is animated.
here is the view controller:
class ViewController: UIViewController {
let data = createData()
var transition = PopAnimator()
var rootView = MainView()
private let collectionView: UICollectionView = {
let viewLayout = UICollectionViewFlowLayout()
let collectionView = UICollectionView(frame: .zero, collectionViewLayout: viewLayout)
collectionView.backgroundColor = .white
return collectionView
}()
private enum LayoutConstant {
static let spacing: CGFloat = 16.0
static let itemHeight: CGFloat = 200.0
}
override func loadView() {
view = rootView
rootView.collectionView.delegate = self
rootView.collectionView.dataSource = self
}
}
class MainView: UIView {
let collectionView: UICollectionView = {
let viewLayout = UICollectionViewFlowLayout()
let collectionView = UICollectionView(frame: .zero, collectionViewLayout: viewLayout)
collectionView.backgroundColor = .white
return collectionView
}()
init() {
super.init(frame: .zero)
setupLayouts()
}
private func setupLayouts() {
backgroundColor = .white
addSubview(collectionView)
collectionView.register(CollectionViewCell.self, forCellWithReuseIdentifier: CollectionViewCell.identifier)
collectionView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
collectionView.topAnchor.constraint(equalTo: safeAreaLayoutGuide.topAnchor),
collectionView.bottomAnchor.constraint(equalTo: safeAreaLayoutGuide.bottomAnchor),
collectionView.leftAnchor.constraint(equalTo: safeAreaLayoutGuide.leftAnchor),
collectionView.rightAnchor.constraint(equalTo: safeAreaLayoutGuide.rightAnchor)
])
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
extension ViewController: UICollectionViewDataSource {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return data.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: CollectionViewCell.identifier, for: indexPath) as! CollectionViewCell
cell.dogImg.image = UIImage(named: data[indexPath.row].image)
return cell
}
}
extension ViewController: UICollectionViewDelegate {
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
let selected = data[indexPath.row]
let vc = OtherViewController()
vc.transitioningDelegate = self
// vc.modalPresentationStyle = .fullScreen
vc.data = selected
self.present(vc, animated: true)
print(selected)
}
}
extension ViewController: UICollectionViewDelegateFlowLayout {
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let width = itemWidth(for: view.frame.width, spacing: LayoutConstant.spacing)
return CGSize(width: width, height: LayoutConstant.itemHeight)
}
func itemWidth(for width: CGFloat, spacing: CGFloat) -> CGFloat {
let itemsInRow: CGFloat = 2
let totalSpacing: CGFloat = 2 * spacing + (itemsInRow - 1) * spacing
let finalWidth = (width - totalSpacing) / itemsInRow
return floor(finalWidth)
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets {
return UIEdgeInsets(top: LayoutConstant.spacing, left: LayoutConstant.spacing, bottom: LayoutConstant.spacing, right: LayoutConstant.spacing)
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat {
return LayoutConstant.spacing
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumInteritemSpacingForSectionAt section: Int) -> CGFloat {
return LayoutConstant.spacing
}
}
extension ViewController: UIViewControllerTransitioningDelegate {
func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning? {
guard let selectedIndexPathCell = self.collectionView.indexPathsForSelectedItems?.first,
let selectedItem = self.collectionView.cellForItem(at: selectedIndexPathCell) as? CollectionViewCell
else { return nil }
guard let originFrame = selectedItem.superview?.convert(selectedItem.frame, to: nil) else {
return transition
}
transition.originFrame = originFrame
transition.presenting = true
return transition
}
func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? {
transition.presenting = false
return transition
}
}
this is the other view that is presented to.
class OtherViewController: UIViewController, UIViewControllerTransitioningDelegate {
var data: DogArr?
var rootView = testView1()
override func viewDidLoad() {
super.viewDidLoad()
self.view.backgroundColor = .systemBackground
}
override func loadView() {
view = rootView
//rootView.inputViewController?.transitioningDelegate = self
rootView.carImg.image = UIImage(named: data?.image ?? "")
rootView.dismissBtn.addTarget(self, action: #selector(dismissingBtn), for: .touchUpInside)
}
#objc func dismissingBtn() {
self.dismiss(animated: true)
}
}
class testView1: UIView {
var carImg: UIImageView = {
let img = UIImageView()
img.contentMode = .scaleAspectFit
img.translatesAutoresizingMaskIntoConstraints = false
return img
}()
var dismissBtn: UIButton = {
let btn = UIButton()
btn.translatesAutoresizingMaskIntoConstraints = false
btn.setTitle("Done", for: .normal)
btn.titleLabel?.font = UIFont.systemFont(ofSize: 25)
btn.titleLabel?.textAlignment = .center
btn.setTitleColor(.label, for: .normal)
btn.contentMode = .scaleAspectFill
return btn
}()
init() {
super.init(frame: .zero)
self.addSubview(carImg)
self.addSubview(dismissBtn)
carImg.translatesAutoresizingMaskIntoConstraints = false
self.backgroundColor = .systemBackground
NSLayoutConstraint.activate([
carImg.topAnchor.constraint(equalTo: self.topAnchor),
carImg.heightAnchor.constraint(equalTo: self.heightAnchor, multiplier: 0.5),
carImg.widthAnchor.constraint(equalTo: carImg.heightAnchor),
dismissBtn.topAnchor.constraint(equalTo: self.carImg.bottomAnchor, constant: 20),
dismissBtn.centerXAnchor.constraint(equalTo: self.dismissBtn.centerXAnchor),
dismissBtn.widthAnchor.constraint(equalToConstant: 150),
dismissBtn.heightAnchor.constraint(equalToConstant: 75)
])
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
I noticed that when I remove the root view and just created everything in the view controller, it seems to work, the thing is that this is just a test for now. The root view in a future project may be very large so I think keeping to a rootview may be a good idea. If there are any other methods similar, please share.
If there is anything I can answer please ask,
Thank you
Related
I have a collection view which takes up the whole screen and should scroll horizontally.
Problem is I can't adjust the size for item while retaining the carousel effect. If I lower the size for item, the cell stacks vertically just like here:
The pink background is the collectionView. The collectionView fills the viewController's view that is why the cells doesn't align horizontally.
The alignment should be like this which scrolls horizontally much like a carousel but the problem is it's too big and it took up the whole view.
What's expected is for the cell and collection view to be around 100 height or 200 height only and can still scroll horizontally.
This is my existing code:
I am using SnapKit for constraints.
ViewController
import UIKit
import SnapKit
class ViewController: UIViewController {
let viewModel: [SeatViewModel] = [.init(text: "single text"), .init(text: "slightly longer text."), .init(text: "very very very very long long text")]
private lazy var collectionView: UICollectionView = {
let layout = UICollectionViewFlowLayout()
layout.scrollDirection = .horizontal
let cv = UICollectionView(frame: .zero, collectionViewLayout: layout)
cv.delegate = self
cv.backgroundColor = .white
cv.register(SeatCardCell.self, forCellWithReuseIdentifier: "SeatCardCell")
return cv
}()
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(collectionView)
collectionView.snp.makeConstraints { make in
make.center.equalToSuperview()
make.width.equalTo(200)
make.height.equalTo(100)
}
collectionView.dataSource = self
collectionView.backgroundColor = .systemPink
}
func lines(label: UILabel) -> Int {
self.collectionView.layoutIfNeeded()
let textSize = CGSize(width: label.frame.size.width, height: CGFloat(Float.infinity))
let rHeight = lroundf(Float(label.sizeThatFits(textSize).height))
let charSize = lroundf(Float(label.font.lineHeight))
let lineCount = rHeight/charSize
return lineCount
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
collectionView.frame = view.bounds
}
}
extension ViewController: UICollectionViewDataSource {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 3
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
guard let collectioCell = collectionView.dequeueReusableCell(withReuseIdentifier: "SeatCardCell", for: indexPath) as? SeatCardCell else { fatalError() }
collectioCell.backgroundColor = [UIColor.green, .red, .black, .brown, .yellow].randomElement()
collectioCell.viewModel = viewModel[indexPath.row]
return collectioCell
}
}
extension ViewController: UICollectionViewDelegateFlowLayout {
func collectionView(_ collectionView: UICollectionView,
layout collectionViewLayout: UICollectionViewLayout,
sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: 200, height: self.collectionView.frame.height)
}
func collectionView(_ collectionView: UICollectionView,
layout collectionViewLayout: UICollectionViewLayout,
minimumInteritemSpacingForSectionAt section: Int) -> CGFloat {
return 1.0
}
func collectionView(_ collectionView: UICollectionView, layout
collectionViewLayout: UICollectionViewLayout,
minimumLineSpacingForSectionAt section: Int) -> CGFloat {
return 1.0
}
}
View Model and Cell
import UIKit
import SnapKit
struct SeatViewModel {
let text: String
}
class SeatCardCell: UICollectionViewCell {
var viewModel: SeatViewModel? {
didSet {
configure()
}
}
// MARK: - Properties
let seatImageView = UIImageView(image: nil)
let seatLabel: CustomLabel = {
let label = CustomLabel()
return label
}()
// MARK: - Lifecycle
override init(frame: CGRect) {
super.init(frame: frame)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
private func configure() {
guard let viewModel = viewModel else { return }
seatLabel.text = viewModel.text
seatLabel.backgroundColor = .cyan
seatLabel.sizeToFit()
seatImageView.image = .strokedCheckmark
let stackView = UIStackView(arrangedSubviews: [seatImageView, seatLabel])
stackView.spacing = 0
stackView.alignment = .center
addSubview(stackView)
seatImageView.snp.makeConstraints { make in
make.size.equalTo(40)
}
stackView.snp.makeConstraints { make in
make.edges.equalToSuperview()
}
}
}
// MARK: - CUSTOM TITLE LABEL
class CustomLabel: UILabel {
override init(frame: CGRect) {
super.init(frame: frame)
setupUI()
}
required init(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
private func setupUI() {
self.textColor = .darkGray
self.textAlignment = .left
self.numberOfLines = 4
}
}
The problem of your code is you set again the collection view frame equal to view frame in viewDidLayoutSubviews.
You just need to remove it and your code will work perfectly fine.
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
// remove it
//collectionView.frame = view.bounds
}
I am trying to make custom Image Slider with collections view. I want to it to be reusable. So I made separate custom class where all collectionView stuff. and then call that class from UIViewController as shown in code below. And my UICollectonViewCell only contains imageView.
Problem is that in second cell. imageView is not being added and on third cell, it also flickers. I tried to debug these issues but could not.
ImageSlider class and UICollectionViewCell class at end end, with collection view stuff:
class ImageSlider: UIView {
var imgArr = [UIImage(named: "one.jpg"), UIImage(named: "3.jpg"), UIImage(named: "two.jpg"), UIImage(named: "4.jpg")]
var sliderCollection : UICollectionView = {
let widthRatio : Float = 16.0
let heightRatio : Float = 9.0
let collecionWidth = UIScreen.main.bounds.width - 30
let layout = UICollectionViewFlowLayout()
let collectionView = UICollectionView(frame: CGRect(x: 15, y: 100, width: collecionWidth, height: CGFloat((Float(collecionWidth)*heightRatio)/widthRatio)), collectionViewLayout: layout)
layout.scrollDirection = .horizontal
layout.minimumLineSpacing = 0
layout.minimumInteritemSpacing = 0
collectionView.backgroundColor = .systemOrange
collectionView.isPagingEnabled = true
// collectionView.isScrollEnabled = true
collectionView.register(ImageSliderCell.self, forCellWithReuseIdentifier: "ImageSliderCell")
return collectionView
}()
}
extension ImageSlider: UICollectionViewDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return imgArr.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "ImageSliderCell", for: indexPath) as! ImageSliderCell
cell.imgView.image = imgArr[indexPath.item]
// cell.imgView.contentMode = .scaleAspectFit
print("cell frame : ", "(\(cell.frame.width), \(cell.frame.height)")
print("imgView frame : ", "(\(cell.imgView.frame.width), \(cell.imgView.frame.height)")
return cell
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: collectionView.frame.width, height: collectionView.frame.height)
}
}
class ImageSliderCell: UICollectionViewCell {
var imgView = UIImageView()
// override func awakeFromNib() {
// self.addSubview(imgView)
// }
override init(frame: CGRect) {
super.init(frame: frame)
imgView.frame = frame
self.addSubview(imgView)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
This is ViewController, where I am calling ImageSlider() class.
class ImageSliderVC: UIViewController, UICollectionViewDelegate {
let imageSlider = ImageSlider()
override func viewDidLoad() {
super.viewDidLoad()
self.view.addSubview(imageSlider.sliderCollection)
imageSlider.sliderCollection.delegate = imageSlider
imageSlider.sliderCollection.dataSource = imageSlider
imageSlider.sliderCollection.reloadData()
}
}
It looks like it does not work without constrains because UICollectionViewCell could be created with zero frame and it translated to imageView inside the cell. You need to add constrains to imageView to make it visible.
extension UIView {
func centerX(inView view: UIView, constant: CGFloat = 0) {
translatesAutoresizingMaskIntoConstraints = false
centerXAnchor.constraint(equalTo: view.centerXAnchor, constant: constant).isActive = true
}
func centerY(inView view: UIView, constant: CGFloat = 0) {
translatesAutoresizingMaskIntoConstraints = false
centerYAnchor.constraint(equalTo: view.centerYAnchor, constant: constant).isActive = true
}
func setDimensions(height: CGFloat, width: CGFloat) {
translatesAutoresizingMaskIntoConstraints = false
heightAnchor.constraint(equalToConstant: height).isActive = true
widthAnchor.constraint(equalToConstant: width).isActive = true
}
func setHeight(_ height: CGFloat) {
translatesAutoresizingMaskIntoConstraints = false
heightAnchor.constraint(equalToConstant: height).isActive = true
}
}
class ImageSliderCell: UICollectionViewCell {
var imgView = UIImageView()
override init(frame: CGRect) {
super.init(frame: frame)
self.addSubview(imgView)
// not sure about the right size of image ...
imgView.setDimensions(height: 100.0, width: 100.0)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
I want to implement a search function in UICollectionView in a UICollectionViewCell.
What i'm trying to achieve is a swipe-able pages with search bar.
Previously i achieved multiple pages with a search bar but not able to swipe and to match with my Android application i need to make it swipe-able.
This is the tutorial i followed on how to make it swipe-able
https://www.letsbuildthatapp.com/course_video?id=75
To make this sound less confusing, the main UICollectionView let's call it MainFrame and the MainFrame stores the two UICollectionViewCell which contains their own UICollectionView let's call it ChildFrame.
So i'm able to parse search text from MainFrame to the ChildFrame and filter the data array in the ChildFrame. Checking it on the console log, everything is working as intended. The filtered result is correct. But my ChildFrame is not updating to the latest data array. Here's the code below.
Here's the code for ChildFrame
class ChildFrameCell1: UICollectionViewCell, UICollectionViewDataSource, UICollectionViewDelegate, UICollectionViewDelegateFlowLayout {
let cellId = "cellId"
var dataArray = [CustomObject]()
var filteredData = [CustomObject]()
var isFiltering: Bool = false
var mainFrame: MainFrame?
lazy var collectionView: UICollectionView = {
let layout = UICollectionViewFlowLayout()
layout.sectionInset = UIEdgeInsets(top: 4, left: 0, bottom: 0, right: 0)
layout.minimumInteritemSpacing = 0.0
let cv = UICollectionView(frame: .zero, collectionViewLayout: layout)
cv.backgroundColor = .white
cv.dataSource = self
cv.delegate = self
return cv
}()
override init(frame: CGRect) {
super.init(frame: frame)
populateCollectionView()
collectionView.register(CustomCell.self, forCellWithReuseIdentifier: cellId)
addSubview(collectionView)
collectionView.anchor(top: self.topAnchor, left: self.leftAnchor, bottom: self.bottomAnchor, right: self.rightAnchor, paddingTop: 0, paddingLeft: 0, paddingBottom: 0, paddingRight: 0, width: 0, height: 0)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func searchTypeAndText(type: String, text: String, filtering: Bool) {
isFiltering = filtering
filteredData = dataArray.filter({( object : CustomObject) -> Bool in
if type == "type" {
return object.type.lowercased().contains(text.lowercased())
} else {
return object.type.lowercased().contains(text.lowercased())
}
})
DispatchQueue.main.async {
self.collectionView.reloadData()
self.collectionView.layoutSubviews()
}
}
func populateCollectionView() {
// load data into dataArray
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
if isFiltering {
// during searching this will be triggered and return the correct count for the filteredData
return filteredData.count
} else {
return dataArray.count
}
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: cellId, for: indexPath) as! CustomCell
var object: CustomObject
// isFiltering is always false even when searching
if isFiltering {
// never called
object = filteredData[indexPath.row]
} else {
// always called even when searching
object = dataArray[indexPath.row]
}
// do cell things here
return cell
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: collectionView.frame.size.width - 8, height: 120)
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat {
return 4
}
func collectionView(_ collectionView: UICollectionView, willDisplay cell: UICollectionViewCell, forItemAt indexPath: IndexPath) {
cell.contentView.layer.masksToBounds = true
let radius = cell.contentView.layer.cornerRadius
cell.layer.shadowPath = UIBezierPath(roundedRect: cell.bounds, cornerRadius: radius).cgPath
}
}
Here's the code for MainFrame
class MainFrame: UICollectionViewController, UICollectionViewDelegateFlowLayout {
let searchController = UISearchController(searchResultsController: nil)
let cellId = "cellId"
let childFrameCell1Id = "cell1Id"
let childFrameCell2Id = "cell2Id"
var childFrameCell1: ChildFrameCell1!
var childFrameCell2: ChildFrameCell2!
lazy var topBar: CustomTopBar = {
let tb = CustomTopBar()
tb.mainFrame = self
return tb
}() // Top bar sliding indicator
override func viewDidLoad() {
super.viewDidLoad()
configureView()
configureSearchBar()
configureCollectionView()
setupTopBar()
}
func configureCollectionView() {
if let flowLayout = collectionView?.collectionViewLayout as? UICollectionViewFlowLayout {
flowLayout.scrollDirection = .horizontal
flowLayout.minimumLineSpacing = 0
flowLayout.minimumInteritemSpacing = 0
}
collectionView.backgroundColor = .white
collectionView.register(ChildFrameCell1.self, forCellWithReuseIdentifier: childFrameCell1Id)
collectionView.register(ChildFrameCell2.self, forCellWithReuseIdentifier: childFrameCell2Id)
collectionView.translatesAutoresizingMaskIntoConstraints = false
collectionView.isPagingEnabled = true
collectionView.showsHorizontalScrollIndicator = false
collectionView.collectionViewLayout.invalidateLayout()
collectionView.contentInset = UIEdgeInsets(top: 45, left: 0, bottom: 0, right: 0)
collectionView.scrollIndicatorInsets = UIEdgeInsets(top: 45, left: 0, bottom: 0, right: 0)
collectionView.contentInsetAdjustmentBehavior = UIScrollView.ContentInsetAdjustmentBehavior.never
childFrameCell1 = ChildFrameCell1()
childFrameCell2 = ChildFrameCell2()
}
func setupTopBar() {
// add top bar
}
func configureView() {
// set up view
}
func configureSearchBar() {
searchController.searchResultsUpdater = self
searchController.obscuresBackgroundDuringPresentation = false
searchController.searchBar.placeholder = "Search"
searchController.searchBar.tintColor = .white
searchController.searchBar.barTintColor = .white
if let tf = searchController.searchBar.value(forKey: "searchField") as? UITextField {
tf.textColor = UIColor.white
if let backgroundView = tf.subviews.first {
backgroundView.backgroundColor = .white
backgroundView.layer.cornerRadius = 10
backgroundView.clipsToBounds = true
}
}
navigationItem.searchController = searchController
definesPresentationContext = true
searchController.searchBar.scopeButtonTitles = ["Type1", "Type2"]
searchController.searchBar.delegate = self
}
func searchBarIsEmpty() -> Bool {
return searchController.searchBar.text?.isEmpty ?? true
}
func isFiltering() -> Bool {
let searchBarScopeIsFiltering = searchController.searchBar.selectedScopeButtonIndex != 0
return searchController.isActive && (!searchBarIsEmpty() || searchBarScopeIsFiltering)
}
func filterContentForSearchText(_ searchText: String, scope: String = "Location") {
let visibleRect = CGRect(origin: collectionView.contentOffset, size: collectionView.bounds.size)
let visiblePoint = CGPoint(x: visibleRect.midX, y: visibleRect.midY)
let visibleIndexPath = collectionView.indexPathForItem(at: visiblePoint)
if visibleIndexPath?.row == 0 {
// find the current view and parse search text into childFrame
childFrameCell1.searchTypeAndText(type: scope, text: searchText, filtering: isFiltering())
} else {
childFrameCell2.searchTypeAndText(type: scope, text: searchText, filtering: isFiltering())
}
}
override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 2 // 2 pages
}
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let id: String
currentView = indexPath.item
if currentView == 0 {
id = childFrameCell1Id
} else {
id = childFrameCell2Id
}
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: id, for: indexPath) as! ChildFrameCell1
cell.mainFrame = self
return cell
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: view.frame.width, height: view.frame.height - 50)
}
}
extension MainFrame: UISearchResultsUpdating {
func updateSearchResults(for searchController: UISearchController) {
let searchBar = searchController.searchBar
let scope = searchBar.scopeButtonTitles![searchBar.selectedScopeButtonIndex]
filterContentForSearchText(searchController.searchBar.text!, scope: scope)
}
}
}
extension MainFrame: UISearchBarDelegate {
func searchBar(_ searchBar: UISearchBar, selectedScopeButtonIndexDidChange selectedScope: Int) {
filterContentForSearchText(searchBar.text!, scope: searchBar.scopeButtonTitles![selectedScope])
}
}
ChildFrameCell2 is a subclass of ChildFrameCell1
When i enter text in the search bar in the MainFrame, it doesn't reload the collectionView to the new filteredData even if filteredData contains data.
I've been trying many ways to do this but still unable to reload the collectionView.
I even tried using DispatchGroup but the result is still the same. Filtering is not an issue, everything works accordingly except for reloading the collectionView.
Thank you for reading this lengthy post.
If you need the code from the MainFrame do let me know.
I'm working on App where I have to manage my horizontal indicator on CollectionviewCell, while scrolling CollectionView and by selecting any Cell.
This is what I’m looking for(Just focus on Horizontal CollectionView)
As I have implemented this But I’m not getting the exact functionality/behavior AND unable to stick horizontal indicator on collectionviewCell while scrolling. I can only stick if i make a horizontal indicator in CollectionViewCell But in this Case I’m unable to apply sliding animation.
This is what I have implemented
Here is my Code Snippet for MENUBAR
import UIKit
class MenuBar: UITableViewHeaderFooterView, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout, UIScrollViewDelegate {
//MARK:- Properties
let cellId = "cellId"
let menuNames = ["Recommeded", "Popular", "Free", "Trending", "Love Songs", " Free Songs"]
var horizontalBarLeftAnchorConstraint : NSLayoutConstraint?
lazy var collectionView : UICollectionView = {
let cv = UICollectionView(frame: .zero, collectionViewLayout: UICollectionViewFlowLayout())
cv.translatesAutoresizingMaskIntoConstraints = false
cv.dataSource = self
cv.delegate = self
cv.showsHorizontalScrollIndicator = false
cv.backgroundColor = UIColor.clear
return cv
}()
let horizontalView : UIView = {
let v = UIView()
v.backgroundColor = UIColor.red
v.translatesAutoresizingMaskIntoConstraints = false
return v
}()
//MARK:- default methods
override init(reuseIdentifier: String?) {
super.init(reuseIdentifier: reuseIdentifier)
setupCollectionView()
setupHorizontalBar()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
//MARK:- Functions
private func setupCollectionView() {
if let flowLayout = collectionView.collectionViewLayout as? UICollectionViewFlowLayout {
flowLayout.scrollDirection = .horizontal
}
addSubview(collectionView)
collectionView.register(MenuCell.self, forCellWithReuseIdentifier: cellId)
collectionView.leadingAnchor.constraint(equalTo: leadingAnchor).isActive = true
collectionView.trailingAnchor.constraint(equalTo: trailingAnchor).isActive = true
collectionView.bottomAnchor.constraint(equalTo: bottomAnchor).isActive = true
collectionView.topAnchor.constraint(equalTo: topAnchor).isActive = true
}
private func setupHorizontalBar() {
addSubview(horizontalView)
let textSize = (menuNames[0] as NSString).size(withAttributes: nil)
let cellSize = textSize.width + 50
let indicatorLineWith = 25/2
let x = (cellSize/2) - CGFloat(indicatorLineWith)
//x,y,w,h
horizontalBarLeftAnchorConstraint =
horizontalView.leftAnchor.constraint(equalTo: leftAnchor, constant: x )
horizontalBarLeftAnchorConstraint?.isActive = true
horizontalView.heightAnchor.constraint(equalToConstant: 5).isActive = true
horizontalView.widthAnchor.constraint(equalToConstant: 25).isActive = true
horizontalView.bottomAnchor.constraint(equalTo: self.bottomAnchor).isActive = true
}
//MARK:- CollectionView methods
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return menuNames.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: cellId, for: indexPath) as! MenuCell
cell.menuName = menuNames[indexPath.row]
return cell
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let size = (menuNames[indexPath.row] as NSString).size(withAttributes: nil)
return CGSize(width: (size.width + 50), height: frame.height)
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat {
return 0
}
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
//getting Cell size on screen
let attributes : UICollectionViewLayoutAttributes = collectionView.layoutAttributesForItem(at: indexPath)!
let indicatorSize = 25/2
let cellRect = attributes.frame
let cellFrameInSuperView = collectionView.convert(cellRect, to: collectionView)
let textSize = (menuNames[indexPath.row] as NSString).size(withAttributes: nil)
let cellSize = textSize.width + 50
let x = (CGFloat(cellFrameInSuperView.origin.x) + (cellSize/2)) - CGFloat(indicatorSize)
horizontalBarLeftAnchorConstraint?.constant = x
UIView.animate(withDuration: 0.75, delay: 0, usingSpringWithDamping: 1, initialSpringVelocity: 1, options: .curveEaseOut, animations: {
collectionView.scrollToItem(at: indexPath, at: .centeredHorizontally, animated: true)
self.layoutIfNeeded()
}, completion: nil)
}
}
Here is my code snippet for MENUCELL:-
import UIKit
//MARK:- CollectionViewBaseCell
class CollectionViewBaseCell : UICollectionViewCell {
override init(frame: CGRect) {
super.init(frame: frame)
setupViews()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func setupViews() {}
}
//MARK:- MenuCell
class MenuCell : CollectionViewBaseCell {
//MARK:- Properties
var menuName : String? {
didSet {
label.text = menuName
}
}
let label : UILabel = {
let lbl = UILabel()
lbl.translatesAutoresizingMaskIntoConstraints = false
lbl.text = "Label"
return lbl
}()
//MARK:- default methods
override func setupViews() {
addSubview(label)
//x,y,w,h Constraint
label.centerXAnchor.constraint(equalTo: centerXAnchor).isActive = true
label.centerYAnchor.constraint(equalTo: centerYAnchor).isActive = true
}
}
TRY THIS:
func scrollViewDidScroll(_ scrollView: UIScrollView) {
if scrollView == self.collectionView{
print( scrollView.contentOffset.x ) // use this contentOffset
}
}
What do i have: A view controller (WhereViewController) with some "child/container"-views. One is a collection view (typeCollectionView). the WhereViewController is embedded in a navigationController:
Screenshot
class WhereViewController: UIViewController {
lazy var progressContainerView: ProgressBarView = {
let containerView = ProgressBarView(frame: CGRect(x: 0, y: 0, width: 0, height: 0))
containerView.translatesAutoresizingMaskIntoConstraints = false
return containerView
}()
let questionLabel: UILabel = {
let label = UILabel()
label.text = "question?"
label.textAlignment = NSTextAlignment.center
label.font = UIFont.init(name: "OpenSans-Italic", size: 14)
label.textColor = UIColor(red:0.33, green:0.33, blue:0.33, alpha:1.0)
label.translatesAutoresizingMaskIntoConstraints = false
return label
}()
let typeCollectionView: WhereCollectionView = {
let collectionView = WhereCollectionView(frame: CGRect(x: 0, y: 0, width: 0, height: 0))
collectionView.translatesAutoresizingMaskIntoConstraints = false
return collectionView
}()
override func viewDidLoad() {
super.viewDidLoad()
navigationItem.title = "Schaden melden"
view.backgroundColor = UIColor.white
view.addSubview(progressContainerView)
view.addSubview(questionLabel)
view.addSubview(typeCollectionView)
setupProgressContainerView()
setupQuestionLabel()
setupTypeCollectionView()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
private func setupProgressContainerView() {
progressContainerView.topAnchor.constraint(equalTo: view.topAnchor, constant: 60).isActive = true
progressContainerView.leftAnchor.constraint(equalTo: view.leftAnchor).isActive = true
progressContainerView.widthAnchor.constraint(equalTo: view.widthAnchor).isActive = true
progressContainerView.heightAnchor.constraint(equalToConstant: 50).isActive = true
}
private func setupQuestionLabel() {
questionLabel.topAnchor.constraint(equalTo: progressContainerView.bottomAnchor, constant: 22).isActive = true
questionLabel.leftAnchor.constraint(equalTo: view.leftAnchor).isActive = true
questionLabel.widthAnchor.constraint(equalTo: view.widthAnchor).isActive = true
questionLabel.heightAnchor.constraint(equalToConstant: 20).isActive = true
}
private func setupTypeCollectionView() {
typeCollectionView.topAnchor.constraint(equalTo: questionLabel.bottomAnchor, constant: 20).isActive = true
typeCollectionView.leftAnchor.constraint(equalTo: view.leftAnchor).isActive = true
typeCollectionView.widthAnchor.constraint(equalTo: view.widthAnchor).isActive = true
typeCollectionView.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
}
}
Want am I trying to do: Within the typeCollectionView: If an item is selected (didSelectItemAt) i want to push to the next viewController via navigationController of the WhereViewController. My typeCollectionView looks like:
class WhereCollectionView: UICollectionView, UICollectionViewDelegateFlowLayout, UICollectionViewDataSource {
var whereViewController: WhereViewController?
let label = ["x", "y", "z"]
override init(frame: CGRect, collectionViewLayout layout: UICollectionViewLayout) {
let layout = UICollectionViewFlowLayout()
super.init(frame: CGRect.zero, collectionViewLayout: layout)
backgroundColor = UIColor.white
delegate = self
dataSource = self
self.register(WhereCollectionViewCell.self, forCellWithReuseIdentifier: "WhereCollectionViewCell")
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func numberOfSections(in collectionView: UICollectionView) -> Int {
// #warning Incomplete implementation, return the number of sections
return 1
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
// #warning Incomplete implementation, return the number of items
return label.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "WhereCollectionViewCell", for: indexPath) as! WhereCollectionViewCell
cell.injureLabel.text = label[indexPath.row]
return cell
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: self.frame.width, height: 75)
}
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
}
}
I've tried to give the whereViewController to the typeCollectionView:
let typeCollectionView: WhereCollectionView = {
let collectionView = WhereCollectionView(frame: CGRect(x: 0, y: 0, width: 0, height: 0))
collectionView.translatesAutoresizingMaskIntoConstraints = false
collectionView.whereViewController = self
return tableView
}()
But get: Cannot assign value of type '(NSObject) -> () -> WhereViewController' to type 'WhereViewController?'
Could someone help me? or isn't it possible to "speak" with the navigationController from the collectionView (containerView)?
I do not use storyboard
Try to:
1) Add extension:
extension UIView {
var parentViewController: UIViewController? {
var parentResponder: UIResponder? = self
while parentResponder != nil {
parentResponder = parentResponder!.next
if parentResponder is UIViewController {
return parentResponder as! UIViewController!
}
}
return nil
}
}
2) Now you can use:
parentViewController?.present(<ControllerName>, animated: true)
Hope it helps
You should conform the view controller to the collection delegate methods and not the collection view itself.
Make WhereViewController conform to UICollectionViewDataSource, UICollectionViewDelegate and UICollectionViewDelegateFlowLayout:
class WhereViewController: UIViewController, UICollectionViewDataSource, UICollectionViewDelegate, UICollectionViewDelegateFlowLayout
In WhereViewController's viewDidLoad method, set up the delegate and the data source:
override func viewDidLoad() {
super.viewDidLoad()
typeCollectionView.dataSource = self
typeCollectionView.delegate = self
[...]
}
Here you can use delegate to send message from CollectionView to
ViewController.
Simple declare a protocol
protocol CustomDelegate: class {
func collectionView(DidSelect : Int)
}
After that make an instance of CustomDelegate protocol in WhereCollectionView Class
class WhereCollectionView: UICollectionView {
weak var customDelegate:CustomDelegate?
}
and in didSelect methods fire the delegate method
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
if let _del = customDelegate {
_del.collectionView(DidSelect : IndexPath.row)
}
}
In ViewController there you are adding collectionview in viewcontroller set delegate to self
override func viewDidLoad() {
super.viewDidLoad()
navigationItem.title = "Schaden melden"
view.backgroundColor = UIColor.white
typeCollectionView.customDelegate = self
view.addSubview(progressContainerView)
view.addSubview(questionLabel)
view.addSubview(typeCollectionView)
setupProgressContainerView()
setupQuestionLabel()
setupTypeCollectionView()
}
Also Implement the Custom delegate method in ViewController
func collectionView(DidSelect : Int) {
/// from there you can do your job
}