UICollectionView inside UIViewController not allowing scroll - ios

Edit1: I am not using a storyboard everything has been added programmatically
Edit2: Updated code snippet
Hello, i've got the following issues with UICollectionView
The CollectionView won't allow you to scroll no matter what you do.
Sometimes the cells even disappear complete leaving you with a white background CollectionView.
The problem occurs when you try to scroll up to view the cells that are not visible.
I've create a CollectionView which has different type of cells.
The CollectionView is nested inside a ViewController below an ImageView.
Constraints are added and they work just fine.
How do i make it scrollable?
GIF representing the problem
ViewController.swift
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
self.navigationItem.title = "Featured"
self.view.backgroundColor = UIColor.white
// Add UImage carousel
let carousel: UIImageView = {
let imageView = UIImageView()
let image = UIImage()
imageView.backgroundColor = UIColor.purple
return imageView
}()
self.view.addSubview(carousel)
carousel.translatesAutoresizingMaskIntoConstraints = false
// Add CollectionView
let featuredControllerLayout = UICollectionViewFlowLayout()
// Add CollectionViewController
featuredControllerLayout.scrollDirection = .vertical
let featuredController = FeaturedCollectionViewController(collectionViewLayout: featuredControllerLayout)
guard let featuredView = featuredController.collectionView else { return }
self.view.addSubview(featuredView)
featuredView.translatesAutoresizingMaskIntoConstraints = false
// Setup Constraints
if #available(iOS 11.0, *) {
let guide = self.view.safeAreaLayoutGuide
let guideSize = guide.layoutFrame.size
carousel.trailingAnchor.constraint(equalTo: guide.trailingAnchor).isActive = true
carousel.leadingAnchor.constraint(equalTo: guide.leadingAnchor).isActive = true
carousel.topAnchor.constraint(equalTo: guide.topAnchor).isActive = true
carousel.frame.size.height = guideSize.width/2
carousel.heightAnchor.constraint(equalToConstant: carousel.frame.size.height).isActive = true
featuredView.trailingAnchor.constraint(equalTo: guide.trailingAnchor).isActive = true
featuredView.leadingAnchor.constraint(equalTo: guide.leadingAnchor).isActive = true
featuredView.topAnchor.constraint(equalTo: carousel.bottomAnchor).isActive = true
featuredView.bottomAnchor.constraint(equalTo: guide.bottomAnchor).isActive = true
}
}

Heyy what you are telling is you are not able to scroll up and see the entire collection view items ?
if that is the case then you have not accounted the height of tabbar controller so you can enable translucent for example I have a git repo I made you can check and let me know if you are stuck anywhere
https://github.com/dwivediashish00/socialApp

Related

Swift: Programmatically add multiple stack view one below the other through a loop

I have a view controller created in the storyboard and the it contains (check image for reference)
ScrollerView
a. StackViewA (image: green)
i. LabelA
ii. LabelB
b. StackViewB (image: green)
i. LabelC
ii. LabelD
I am fetching data from the API and am able to show that data in those labels.
Now, the 3rd set of data that I am fetching is dynamic, meaning it can be 2 more StackView (image: red) under the second StackView or 3 more etc.
I am guessing that I have the add that StackView programmatically in the controller inside the loop so that is created according to the loop.
Currently my 3rd StackView is also created in the storyboard and therefore it is showing only the last data from 3rd set after looping through them.
How do I solve that?
More specifically:
How can I add a StackView inside the ScrollerView created in the storyboard.
How do I contains it to position itself below the 2nd StackView also created in the storyboard.
Update
class InfoDetailsViewController: UIViewController {
// MARK: - Outlets
#IBOutlet weak var infoStack: UIStackView!
#IBOutlet weak var mainScroll: UIScrollView!
static var apiResp: APIDetailsResponse.APIReturn?
let infos: [APIDetailsResponse.Info] = (APIDetailsViewController.apiResp?.content?.infos)!
override func viewDidLoad() {
super.viewDidLoad()
infoStack.spacing = 25.0
for info in infos {
let addInfoTitle = UILabel()
addInfoTitle.font = .preferredFont(forTextStyle: .body)
addInfoTitle.backgroundColor = .orange
addInfoTitle.translatesAutoresizingMaskIntoConstraints = false
let addInfoContent = UITextView()
addInfoContent.font = .preferredFont(forTextStyle: .body)
addInfoContent.backgroundColor = .green
addInfoContent.translatesAutoresizingMaskIntoConstraints = false
addInfoTitle.text = "\(String(describing: (info.info_title)!))"
let htmlString = "\(String(describing: (info.information)!))"
// works even without <html><body> </body></html> tags, BTW
let data = htmlString.data(using: String.Encoding.unicode)!
let attrStr = try? NSAttributedString(
data: data,
options: [NSAttributedString.DocumentReadingOptionKey.documentType: NSAttributedString.DocumentType.html],
documentAttributes: nil)
addInfoContent.attributedText = attrStr
let childStackView = UIStackView(arrangedSubviews: [addInfoTitle, addInfoContent])
childStackView.alignment = .fill
childStackView.axis = .vertical
childStackView.distribution = .fill
childStackView.spacing = 5.0
childStackView.translatesAutoresizingMaskIntoConstraints = false
infoStack.addArrangedSubview(childStackView)
}
}
Currently I have this. What is happening now is no matter how many data the array is returning, I am always getting title and content for the first one and only title for each consecutive data.
So the best solution is when you create your scrollview add a stack view inside it. And whenever you are creating stack view dynamically add under that stack view which is inside scrollview.
So in this case you new stack view will gets stacked under your outer stack view in proper manner.
ScrollView -> 1 Stackview -> Multiple Dynamic stack views in loop
lets say you already have stack view named ParentStackview from your storyboard. Then follow these steps
lazy var childStackView: UIStackView = {
let stackView = UIStackView()
stackView.alignment = .center
stackView.axis = .vertical
stackView.distribution = .equalCentering
stackView.spacing = 30.0
return stackView
}()
public func viewDidLoad() {
ParentStackview.alignment = .center
ParentStackview.axis = .vertical
ParentStackview.distribution = .equalCentering
ParentStackview.spacing = 10.0
for eachChild in data {
ParentStackView.addArrangedSubview(childStackView)
childStackView.translatesAutoresizingMaskIntoConstraints = false
childStackView.widthAnchor.constraint(equalTo: securityScrollView.widthAnchor)
//set height too
// this below function you can use to add eachChild data to you child stackview
configureStackview(childstackView, eachChild)
}
}
Enjoy!

Unsure how to mimic desired UI functionality

So I found the following UI pattern online and I have been attempting to implement it in Xcode. However, I have been unsuccessful. I am unsure as to whether to create the optimal approach would be to
create three different UIViewControllers (in which case I am not sure as to how to get them to animate in and out of view/how to get them to overlap one another)
or to use a UITableView with custom overlapping cells. However, I am not sure whether this approach will allow me to animate properly upon pressing each cell. For this second approach, I saw this post, but this solution does not allow for touch interaction in the overlapping areas, something which I need.
I looked for libraries online that would allow for functionality such as this, but I was unsuccessful in finding any. The animation I am trying to achieve can be found here.
I would use a StackView to hold all the views of your ViewControllers.
Then you can set the stack view's spacing property to a negative value to make the views overlap. If you wish show the complete view of one of the view controllers on tap, you add a tap gesture recognizer to that view that changes the stackView's spacing for just that view to 0.
A really simple example:
class PlaygroundViewController: UIViewController {
let firstViewController = UIViewController()
let secondViewController = UIViewController()
let thirdViewController = UIViewController()
lazy var viewControllersToAdd = [firstViewController, secondViewController, thirdViewController]
let heightOfView: CGFloat = 300
let viewOverlap: CGFloat = 200
let stackView = UIStackView()
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = UIColor.white
firstViewController.view.backgroundColor = .red
secondViewController.view.backgroundColor = .blue
thirdViewController.view.backgroundColor = .green
stackView.translatesAutoresizingMaskIntoConstraints = false
stackView.axis = .vertical
stackView.spacing = -viewOverlap
viewControllersToAdd.forEach { (controller: UIViewController) in
if let childView = controller.view {
stackView.addArrangedSubview(childView)
NSLayoutConstraint.activate([
childView.heightAnchor.constraint(equalToConstant: heightOfView),
childView.widthAnchor.constraint(equalTo: stackView.widthAnchor)
])
let gestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(didTapChildView(sender:)))
childView.addGestureRecognizer(gestureRecognizer)
childView.isUserInteractionEnabled = true
}
addChild(controller)
controller.didMove(toParent: self)
}
view.addSubview(stackView)
NSLayoutConstraint.activate([
stackView.rightAnchor.constraint(equalTo: view.rightAnchor),
stackView.leftAnchor.constraint(equalTo: view.leftAnchor),
stackView.topAnchor.constraint(equalTo: view.topAnchor),
])
}
#objc func didTapChildView(sender: UITapGestureRecognizer) {
if let targetView = sender.view {
UIView.animate(withDuration: 0.3, animations: {
let currentSpacing = self.stackView.customSpacing(after: targetView)
if currentSpacing == 0 {
// targetView is already expanded, collapse it
self.stackView.setCustomSpacing(-self.viewOverlap, after: targetView)
} else {
// expand view
self.stackView.setCustomSpacing(0, after: targetView)
}
})
}
}
}

Add a non-scrolling view to a UITableView

I want to stop the user interacting with a tableview while I get some data.
So I can show and remove a view such that:
var dimmingView: UIView?
Is added and removed with:
func showLoading() {
let dimmingView = UIView(frame: view.frame)
dimmingView.backgroundColor = .darkGray
view.addSubview(dimmingView)
let activityIndicator = UIActivityIndicatorView(style: .whiteLarge)
activityIndicator.translatesAutoresizingMaskIntoConstraints = false
dimmingView.addSubview(activityIndicator)
NSLayoutConstraint.activate([
activityIndicator.centerXAnchor.constraint(equalTo: dimmingView.centerXAnchor),
activityIndicator.centerYAnchor.constraint(equalTo: dimmingView.centerYAnchor)
])
activityIndicator.startAnimating()
self.dimmingView = dimmingView
}
And also
func removeLoading() {
dimmingView?.removeFromSuperview()
}
However, when added it moves along with the table (scrolls). If I stop the table scrolling when the new data is loaded it presents behind the UINavigationBar (the first cell at least). If I try to add to the navigationcontroller.view navigation.controller is nil (so I can't).
What is the best approach?

Swift 4: Mask not working for UIImage?

I'm running the code below (simplified to important blocks) trying to make a side menu animation like the one done in this video: https://www.youtube.com/watch?v=ej5laXv2dzQ&vl=en. I'm trying to do the entire project without using Storyboard (heard it's a good practice, I'm sorta a newbie to iOS development lol), which means I need to create the mask for the side menu programmatically, and have changed some code to fit my needs. Whenever I run the code below, however, the image view that I'm setting the mask on disappears. The program builds, but the side menu image view just isn't appearing (image shown below). Where am I going wrong? Thanks.
let maskView: UIImageView = {
let iv = UIImageView()
iv.backgroundColor = .blue
return iv
}()
let sideMenu: UIImageView = {
let iv = UIImageView()
iv.image = "example.png"
iv.frame.origin.x = -80 //Setting side menu image view to off the left side of the screen so that it can slide in when tapped
return iv
}()
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(maskView)
view.addSubview(sideMenu)
maskView.translatesAutoresizingMaskIntoConstraints = false
maskView.leftAnchor.constraint(equalTo: view.leftAnchor).isActive = true
maskView.widthAnchor.constraint(equalTo: 60).isActive = true
maskView.heightAnchor.constraint(equalTo: view.frame.height).isActive = true
sideMenu.translatesAutoresizingMaskIntoConstraints = false
sideMenu.rightAnchor.constraint(equalTo: view.leftAnchor).isActive = true // So when tapped, it slides 80px to the right, entering the screen
sideMenu.widthAnchor.constraint(equalTo: 80).isActive = true
sideMenu.heightAnchor.constraint(equalTo: view.frame.height).isActive = true
sideMenu.mask = maskView // this is always the line that makes the sideMenu disappear
}
Image of the issue (should have a white background for the side menu on the left):
Image
You need to set Y constraint like top or centerY
maskView.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
sideMenu.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true

UIView on top of a Container View

Is it possible to display an UIView on top of a container View?
I want to add a view with a few opacity background to still see my container View. But everything i tried made either my containerView disappear completely or on top of my View. I tried via Storyboard and code.
I'm sure I'm missing something.
just add your view to the view property of your container controller's container
simple:
let viewYouWantToAddSubviewTo = parent?.view
detail:
import UIKit
class CustomNavigationViewController: UINavigationController {
override func viewDidLoad() {
super.viewDidLoad()
setupViews()
}
func setupViews() {
let layout = UICollectionViewFlowLayout()
let rootVC = HomeCollectionViewController(collectionViewLayout: layout)
viewControllers = [rootVC]
let v = UIView()
v.backgroundColor = UIColor.blue
v.layer.opacity = 0.4
v.translatesAutoresizingMaskIntoConstraints = false
// add your view to this view of the controller's container
let vv = (parent?.view)!
vv.addSubview(v)
// constraints for v
v.leftAnchor.constraint(equalTo: vv.leftAnchor).isActive = true
v.rightAnchor.constraint(equalTo: vv.rightAnchor).isActive = true
v.topAnchor.constraint(equalTo: vv.topAnchor).isActive = true
v.bottomAnchor.constraint(equalTo: vv.bottomAnchor).isActive = true
}
}
result:

Resources