Nest a UIScrollView in Swift - ios

I have a scrollview of an array of images, it works for the images to scroll left and right but I cannot scroll down, instead I am able to scroll my tableview cells...I need to be able to scroll downwards and not have my array of images fixed on the view, is there any ways?
the codes i used are :
override func viewDidLoad() {
super.viewDidLoad()
scrollView.frame = view.frame
imageArray = [UIImage(named:"adsImage3")!,UIImage(named:"adsImage2")!,UIImage(named:"adsImage")!]
navigationController?.navigationBar.barTintColor = UIColor.white
for i in 0..<imageArray.count {
let imageView = UIImageView()
imageView.contentMode = .scaleAspectFit
imageView.clipsToBounds = true
imageView.image = imageArray[i]
let xPosition = self.view.frame.width * CGFloat(i)
imageView.frame = CGRect(x: xPosition, y: 0, width: self.scrollView.frame.width, height: 380)
scrollView.contentSize.width = scrollView.frame.width * CGFloat(i + 1)
scrollView.addSubview(imageView)
}
}
as for my storyboard, it looks like:
if I were to put my scrollview in my tableview, my code would be
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell: MainMenuRowSelectionTableViewCell = self.tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) as! MainMenuRowSelectionTableViewCell
var cellColors = ["#FFB3B3","#FFFFFF"]
cell.contentView.backgroundColor = UIColor(hexString: cellColors[indexPath.row % cellColors.count])
imageArray = [UIImage(named:"adsImage3")!,UIImage(named:"adsImage2")!,UIImage(named:"adsImage")!]
for i in 0..<imageArray.count {
let imageView = UIImageView()
imageView.contentMode = .scaleAspectFit
imageView.clipsToBounds = true
imageView.image = imageArray[i]
}
return cell
}
and my storyboard
this way,my scroll view wouldnt appear at all

I need to be able to scroll downwards and not have my array of images fixed on the view
A table view is a vertical scroll view. You should turn your horizontal scroll view of images into the header view for the table view (i.e. its tableHeaderView), and make the table view occupy the whole screen. Now you can scroll the table and you'll scroll the scroll view of images up out of the screen.

Yes, this can be done. A good reference for nested scroll views is Apple Documentation There is sample code available there as well.
In short, you will likely need to create:
A UIScrollView that fills the view and can scroll vertically,
A UIScrollView within that for the images and can scroll horizontally, and
Your UITableView also placed within the first scroll view.

Related

How to prevent data overlapping in UITableView after scrolling

My data displays correctly before any scrolling happens on the UITableView. Once I scroll I see data overlapping and from my understanding it is because cells get reused! I now understand the concept but not really understanding what my solution needs to be or how/where to implement one (sorry i am very new to swift). I found this Why do my UITableView cells overlap on scroll? but again don't really know how to apply it to my code.
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
cell.textLabel?.lineBreakMode = .byWordWrapping
let userNameLabel = UILabel()
userNameLabel.text = activeUsers[indexPath.row].userFirstName
userNameLabel.font = userNameLabel.font.withSize(10)
userNameLabel.sizeToFit()
let userProfilePic = UIImageView(frame: CGRect(x: 0, y: 0, width: 40, height: 40))
userProfilePic.image = activeUsers[indexPath.row].profilePicImage
userProfilePic.layer.borderWidth = 1.0
userProfilePic.layer.masksToBounds = false
userProfilePic.layer.borderColor = WhistleColors.primaryBorderColor.cgColor
userProfilePic.layer.cornerRadius = userProfilePic.frame.size.height / 2
userProfilePic.clipsToBounds = true
userProfilePic.contentMode = .scaleAspectFill
let userStackView = UIStackView(arrangedSubviews: [userProfilePic, userNameLabel])
userStackView.alignment = .center
userStackView.distribution = .fillEqually
userStackView.axis = .vertical
cell.addSubview(userStackView)
userStackView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate(
[
userStackView.centerXAnchor.constraint(equalTo: cell.contentView.centerXAnchor, constant: cell.frame.size.width / 2),
userStackView.centerYAnchor.constraint(equalTo: cell.contentView.centerYAnchor, constant: cell.frame.size.height / 2),
]
)
userProfilePic.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate(
[
userProfilePic.widthAnchor.constraint(equalToConstant: 40),
userProfilePic.heightAnchor.constraint(equalToConstant: 40)
]
)
return cell
}
Concept of tableview is to reuse cells and it's subviews. You are creating subviews on each cell every time when it reloads. Check https://sahilpathania1997.medium.com/how-to-start-with-tableview-in-swift-bf273a8bbabe
I was able to fix my issue by watching this https://www.youtube.com/watch?v=Pu7B7uEzP18 and implementing a custom cell class with a prepareForReuse() method.
The first youtube video I ever watched around UITableViews and loading data never went over the concept of "reusable cells". Wondering how many issues like this I will encounter in the future because a concept was not discussed in my learning process! Oh well. Onward. Thanks all
dont use cellForRowAt for creating ui properties.
First of all I suggest you pLease use custom cell for UI creation.
Otherwise its overlap.

ScrollView Doesn't Scroll in collectionViewCell

I am creating a collage app. The functionality I am looking at in my collectionViewCell is exactly like this: How to make a UIImage Scrollable inside a UIScrollView?
What I have done till now:
Created a custom collectionViewCell embedded a UIScrollView in it and an imageView inside the scrollView. I have set their constraints. Following is my code for collectionViewCell :
override init(frame: CGRect) {
super.init(frame: frame)
// addSubview(scrollView)
insertSubview(scrollView, at: 0)
contentView.isUserInteractionEnabled = true
scrollView.contentMode = .scaleAspectFill
scrollView.translatesAutoresizingMaskIntoConstraints = false
scrollView.addSubview(imageView)
scrollContentView.translatesAutoresizingMaskIntoConstraints = false
scrollView.isScrollEnabled = true
imageView.isUserInteractionEnabled = false
scrollView.delegate = self
scrollView.maximumZoomScale = 5.0
scrollView.backgroundColor = .red
scrollViewDidScroll(scrollView)
// scrollViewWillBeginDragging(scrollView)
let constraints = [
// Scroll View Constraints
scrollView.topAnchor.constraint(equalTo: contentView.topAnchor,constant: 0.0)
scrollView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor,constant: 0.0),
scrollView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor,constant: 0.0),
scrollView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor,constant: 0.0),
// ImageView Constraints
imageView.topAnchor.constraint(equalTo: self.scrollView.topAnchor),
imageView.bottomAnchor.constraint(equalTo: self.scrollView.bottomAnchor),
imageView.trailingAnchor.constraint(equalTo: self.scrollView.trailingAnchor),
imageView.leadingAnchor.constraint(equalTo: self.scrollView.leadingAnchor)
]
NSLayoutConstraint.activate(constraints)
}
Another issue I face here is if I do insertSubview() instead of addSubview my collectionView function for didselectItemAt works fine else if I use addSubView the didselectItemFunction doesn't execute. Code For didSelectItem :
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
if selectedImage[indexPath.row] != UIImage(named: "empty-image") {
collectionViewCellClass.scrollView.isUserInteractionEnabled = true
collectionViewCellClass.scrollViewDidScroll(collectionViewCellClass.scrollView)
collectionViewCellClass.scrollView.showsHorizontalScrollIndicator = true
}else {
collectionViewCellClass.imageView.isUserInteractionEnabled = false
self.currentIndex = indexPath.row
if UIImagePickerController.isSourceTypeAvailable(.photoLibrary){
imagePicker.delegate = self
imagePicker.allowsEditing = false
imagePicker.sourceType = .savedPhotosAlbum
present(imagePicker, animated: true, completion: nil)
}
}
}
Code for CellForItemAt IndexPath :
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as! CollectionViewCell
cell.imageView.image = selectedImage[indexPath.item]
// TODO: Round corners of the cell
cell.scrollView.frame = CGRect(x: 0, y: 0, width: cell.frame.size.width, height: cell.frame.size.height)
// cell.scrollView.contentSize = CGSize(width: cell.imageView.image!.size.width * 2, height: cell.imageView.image!.size.height * 2)
cell.scrollView.contentSize = cell.imageView.image?.size ?? .zero
cell.scrollView.contentMode = .center
cell.scrollView.isScrollEnabled = true
// cell.scrollContentView.frame = CGRect(x: 0, y: 0, width: cell.frame.size.width, height: cell.frame.size.height)
cell.imageView.frame = CGRect(x: 0, y: 0, width: cell.imageView.image!.size.width, height: cell.imageView.image!.size.height)
// cell.imageView.clipsToBounds = true
cell.backgroundColor = .black
return cell
}
I have tried changing the size of ImageView = cell.frame.size still no luck and changed the size for scrollView Frame as well to imageView.image.size but for some reason scrollView doesn't scroll.
Insertview vs addSubview
addSubview: add the view at the frontmost.
insertSubview: add the view at a specific index.
when you add your scrollview it using addSubView method it will block all the user interaction for the view's that are below it. That's why didSelect event is not triggered.
see this question
To make the scrollview scroll you need to set its contentSize which should be greater than scrollview's frame. try adding an image whose size is greater than collectionview cell.
scrollView.contentSize = imageView.bounds.size
Try adding the scrollView to the cells contentView:
self.contentView.addSubview(scrollView)
I finally found the Solution, following is what worked for me :
if selectedImage[indexPath.row] != UIImage(named: "empty-image"){
cell.contentView.isUserInteractionEnabled = true
}else{
cell.contentView.isUserInteractionEnabled = false
}
The UIImage(named: "empty-image") is the image that appears when a user doesn't have made any selection for his image. I wrote the above code in the cellForItemAt function. Also, I had to set imageView.frame equal to the original width and height of the image.
cell.imageView.frame = CGRect(x: 0, y: 0, width: cell.imageView.image!.size.width, height: cell.imageView.image!.size.height)
The above solution solved my problem. Hopefully, it will help someone.

Question about putting a fixed button in a static tableviewController in Swift

I made a scrollable screen with a static tableview.
But I want to create a UIButton that is independent of scrolling this table view.
I want to anchor it to the bottom of the view as shown below.
Is it possible to dock a button like that at the bottom of the screen in a scrollable static tableview?
You should create a footer view for tableView to do this.
Programmatically
let footerView = UIView()
footerView.backgroundColor = .red // your color
// You have to give height and width yourself
let button = UIButton(frame: CGRect(x: 0, y: 0, width: 100, height: 50))
button.setTitle("<title>", for: .normal)
button.addTarget(self, action: #selector(didTapButton), for: .touchUpInside)
footerView.addSubview(button)
via Interface Builder
Simply you should drag and drop UIView (CMD + Shift + L > type uiview) to your tableView and then add a button to your footerView, set constraints.
Then you should add this footerView to your tableView
tableView.tableFooterView = footerView
Create an action for your button
#objc func didTapButton(_ sender: UIButton) {
print("didTapButton called")
}

asynchronously loading images in swift

I have an image that I want shown at the top of the view in a table view cell.
I have the following code that asynchronously loads that image and adds it to the cell:
if let image = value?["image"] {
let bookImageUrl = URL(string: image as! String)
let data = try? Data(contentsOf: bookImageUrl!) //make sure your image in this url does exist, otherwise unwrap in a if let check / try-catch
DispatchQueue.main.async {
if data != nil {
let imageView = UIImageView(image: UIImage(data: data!));
imageView.frame = CGRect(x: 0,
y: 95,
width: cell.frame.size.width,
height: 150)
imageView.contentMode = .scaleAspectFill
imageView.isUserInteractionEnabled = false
cell.addSubview(imageView)
let label = UILabel(frame: CGRect(x: 10,
y: cell.frame.size.height-60,
width: cell.frame.size.width,
height: 50));
label.textColor = UIColor.black
label.backgroundColor = UIColor.init(red: 1, green: 1, blue: 1, alpha: 0.5)
label.font = UIFont(name: "CeraPro-Bold", size: 16)
label.text = " \(title)"
cell.addSubview(label)
}
}
}
Unfortunately, sometimes the image does not get loaded. Am I doing something wrong?
If you are using tableView.dequeueReusableCell(withIdentifier: "cellIdentifier", for: indexPath) in func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell, here UITableView do reusing of those cells dequeueReusableCell(withIdentifier:)
So as with the problem you mentioned is with some times the images do not get shown, this might be because of the UITableView is reusing the cells, so, it will be better if you use else for every if statement. Or you can use something like, SDWebImage or Kingfisher for image caching.
Also note that, as the UITableView does reuse every cell so it will be better to remove all subviews or create a variable in class for 'UITableViewCell' before adding any view over programmatically, as in your code cell.addSubview(imageView) and cell.addSubview(label) the imageView and label is getting initialized and added over and over for every reuse and every URL check, Here what you can do is create a variable for label and imageview in the cell and initialize only if the variable is nil and assign the image afterwards.

How to add UIImage and UILabel if UITableView has no data

I've looked into many answers but either it's only UILabel or UIImage not both. So after trying to implement it I finally found that we cannot do two tableView.backgroundView. Here is what I've done so far:
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int
{
let noDataLabel: UILabel = UILabel(frame: CGRect(x: 0, y: 0, width: tableView.bounds.size.width, height: tableView.bounds.size.height))
let image = UIImage(named: "noData")
let noDataImage = UIImageView(image: image)
noDataImage.frame = CGRect(x: 0, y: 10, width: 20, height: 20)
if allData.count == 0 {
noDataLabel.isHidden = false
noDataImage.isHidden = false
noDataLabel.text = "No data added. Add new entry \nby pressing the add icon on top right."
noDataLabel.textColor = UIColor.black
noDataLabel.numberOfLines = 3
noDataLabel.backgroundColor = UIColor.white
noDataLabel.textAlignment = .center
//what to do from here
tableView.backgroundView = noDataImage
tableView.backgroundView = noDataLabel
//end
tableView.separatorStyle = .none
return 0;
}
else {
noDataLabel.backgroundColor = UIColor.clear
noDataLabel.isHidden = true
noDataImage.isHidden = true
tableView.backgroundView = nil
return allData.count
}
I want to show an image and below that image I want to show a UILabel. How can I achieve this?
You need to create a view with subviews your image and label
var backgroundView =UIView(frame: CGRectMake(0, 0, your_width, your_height))
backgroundView.addSubview(noDataImage)
backgroundView.addSubview(noDataLabel)
tableView.backgroundView=backgroundView;
Note: Adjust the frame of noDataImage and noDataLabel as per your use.
backgroundView Property
The background view of the table view.
Declaration
Swift
var backgroundView: UIView?
Objective-C
#property(nonatomic, readwrite, retain) UIView *backgroundView
Discussion
A table view’s background view is automatically resized to match the size of the table view. This view is placed as a subview of the table view behind all cells, header views, and footer views.
You must set this property to nil to set the background color of the table view.

Resources