UIScrollView in UICollectionViewCell with Swift - swift

I'm new in IOS. I try to create view images app that show fullscreen size images can zoom and when user zoom out images smaller than fullscreen size, image return fullscreen size and my idea is use UICollectionView set paging Enable , scrollDirection Horizontal. So my FlowLayout will be like this
var screenSize: CGRect!
override func viewDidLoad() {
super.viewDidLoad()
screenSize = UIScreen.mainScreen().bounds
let layout: UICollectionViewFlowLayout = UICollectionViewFlowLayout()
layout.sectionInset = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0)
layout.itemSize = CGSize(width: screenSize.width, height: screenSize.height)
layout.minimumInteritemSpacing = 0
layout.minimumLineSpacing = 0
layout.scrollDirection = UICollectionViewScrollDirection.Horizontal
self.collectionView!.collectionViewLayout = layout
self.view.addSubview(collectionView!)
my CollectionViewCell identifier name "Cell" I drag ScrollView in it and drag image in that ScrollView
I outlet that two item in my CollectionViewCell Class like this
import UIKit
class MyCollectionCell: UICollectionViewCell, UIScrollViewDelegate{
#IBOutlet weak var scrollCell: UIScrollView!
#IBOutlet weak var imgTest: UIImageView!
}
next step in CollectionViewController set my dummy image something like this
var imageName:[String] = ["audi.png", "benz.png", "flok.png", "audi.png", "benz.png", "flok.png", "audi.png", "benz.png", "flok.png"]
and go to cellForItemAtIndexPath
override func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCellWithReuseIdentifier("Cell", forIndexPath: indexPath) as! MyCollectionCell
cell.layer.borderWidth = 0
cell.frame.size.width = screenSize.width
cell.frame.size.height = screenSize.height
// set cell size become fullscreen
cell.imgTest.image = UIImage(named: imageName[indexPath.row])
cell.imgTest.frame = CGRect(origin: CGPoint(x: 0, y: 0), size:cell.imgTest.image!.size)
//call my dummy image to cell
cell.scrollCell.addSubview(cell.imgTest)
cell.scrollCell.contentSize = cell.imgTest.image!.size
let scaleHeight = cell.scrollCell.frame.size.height / cell.scrollCell.contentSize.height
let scaleWidth = cell.scrollCell.frame.size.width / cell.scrollCell.contentSize.width
let minScale = min(scaleWidth, scaleHeight)
cell.scrollCell.minimumZoomScale = minScale
cell.scrollCell.maximumZoomScale = 1.0
cell.scrollCell.zoomScale = minScale
var doubleTapRecognizer = UITapGestureRecognizer(target: self, action: "scrollviewDoubleTapped")
doubleTapRecognizer.numberOfTapsRequired = 2
doubleTapRecognizer.numberOfTapsRequired = 1
cell.scrollCell.addGestureRecognizer(doubleTapRecognizer)
I run this project and the problem is
scroll size don't equal my image size but over
my image can't zoom (I already check "user interaction enabled")
I try using pinch gesture to solve it but I don't know how to set minimumzoomscale to it and the images can't scroll when zoom in
By this code I try using image from json with SDWebImage I change code in cellForItemAtIndexPath from call dummy image to this
cell.imgTest.sd_setImageWithURL(NSURL(string: dataArray[indexPath.row]), placeholderImage: UIImage(named: "loading.png"))
but this way Thread 1 : EXC_BAD_INSTRUCTION (code=EXC_1386_INVOP,subcode=0x0)
Where I go from here can anyone guide me in swift please

instead of a collection view use a scrollview, much easier, and more flexible. Check out this link very good tutorial.. You can merge the scrolled page and scroll zoom. UIScrollView Tutorial: Getting Started

Related

UICollectionView: Resizing cells with animations and custom layouts

I have a collection view with a custom layout and a diffable datasource. I would like to resize the cells with an animation.
With a custom layout: it looks like the content of the cells scaled to the new size during the animation. Only when the cells reaches its final size, it is finally redrawn (see animation below)
With a stock flow layout the subviews are laid out correctly during the animation: the label remains at the top, no resizing and same for the switch.
So I would assume that the issue is in the layout implementation, what could be added to the layout allow a correct layout during the animation?
Description
The custom layout used in this example is FSQCollectionViewAlignedLayout, I also got it with the custom I used in my code. I noticed that for some cases, it is important to always return the same layout attributes objects, which FSQCollectionViewAlignedLayout doesn't do. I modified it to not return copies, and the result is the same.
For the cells (defined in the xib), the contentMode of the cell and its contentView are set to .top. The cell's contentView contains 2 subviews: a label (laid out in layoutSubviews) and a switch (laid out with constraints, but its presence has no effect on the animation).
The code of the controller is:
class ViewController: UIViewController, UICollectionViewDelegateFlowLayout {
enum Section {
case main
}
#IBOutlet weak var collectionView : UICollectionView!
var layout = FSQCollectionViewAlignedLayout()
var dataSource : UICollectionViewDiffableDataSource<Section, String>!
var cellHeight : CGFloat = 100
override func loadView() {
super.loadView()
layout.defaultCellSize = CGSize(width: 150, height: 100)
collectionView.setCollectionViewLayout(layout, animated: false) // uncommented to use the stock flow layout
dataSource = UICollectionViewDiffableDataSource(collectionView: collectionView, cellProvider: { cv, indexPath, item in
guard let cell = cv.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as? Cell else { return nil }
cell.label.text = item
return cell
})
var snapshot = NSDiffableDataSourceSnapshot<Section, String>()
snapshot.appendSections([.main])
snapshot.appendItems(["1", "2"], toSection: .main)
dataSource.apply(snapshot)
}
override func viewDidLoad() {
super.viewDidLoad()
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: 150, height: cellHeight)
}
#IBAction func resizeAction(_ sender: Any) {
if let layout = collectionView.collectionViewLayout as? FSQCollectionViewAlignedLayout {
UIView.animate(withDuration: 0.25) {
layout.defaultCellSize.height += 50
let invalidation = FSQCollectionViewAlignedLayoutInvalidationContext()
invalidation.invalidateItems(at: [[0, 0], [0, 1]])
layout.invalidateLayout(with: invalidation)
self.view.layoutIfNeeded()
}
}
if let layout = collectionView.collectionViewLayout as? UICollectionViewFlowLayout {
UIView.animate(withDuration: 0.25) {
self.cellHeight += 50
let invalidation = UICollectionViewFlowLayoutInvalidationContext()
invalidation.invalidateItems(at: [[0, 0], [0, 1]])
layout.invalidateLayout(with: invalidation)
self.view.layoutIfNeeded()
}
}
}
}
class Cell : UICollectionViewCell {
#IBOutlet weak var label: UILabel!
override func layoutSubviews() {
super.layoutSubviews()
label.frame = CGRect(x: 0, y: 0, width: bounds.width, height: 30)
}
}
I spent a developer support ticket on this question. The answer was that custom animations when reloading cells are not supported.
So as a workaround, I ended up creating new cells instances that I add to the view hierarchy, then add autolayout constraints so that they overlap exactly the cells being resized. The animation is then run on those newly added cells. At the end of the animation, these cells are removed.

Access CollectionView Cell Content (change width of inner view)

I want to have a uiview as cell background with changing width. This should be used as a progress-/ statusbar.
The problem:
I can’t change the width of the statusbar from my collectionViewController (only from the CollectionViewCellController)....
I manually set the width to 5 in layoutSubviews(). Then I changed it to 80 in setStatusBar(...) which I called in the other class. The width stays at 5 :(
Here is my code
import UIKit
class MyCollectionViewCell: UICollectionViewCell {
#IBOutlet weak var myLabel: UILabel!
#IBOutlet weak var statusbar: UIView!
override func layoutSubviews() {
// cell rounded section
self.layer.cornerRadius = 15.0
self.layer.masksToBounds = true
super.layoutSubviews()
statusbar.frame = CGRect(x: 0, y: 0, width: 5, height: self.frame.height)
}
}
extension MyCollectionViewCell{
func setStatusbar(completedTasks: Int, totalTasks: Int){
print(self.frame.width)
let newWidth = 80 //(self.frame.width * (CGFloat(completedTasks / totalTasks)))
statusbar.backgroundColor = .orange
statusbar.frame = CGRect(x: 0, y: 0, width: newWidth, height: Int(self.frame.height))
reloadInputViews()
}
}
And this is where I call the function setStatusbar(...) - where I want to change the width, but DOESNT work:
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
// get a reference to our storyboard cell
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: reuseIdentifier, for: indexPath as IndexPath) as! MyCollectionViewCell
// Use the outlet in our custom class to get a reference to the UILabel in the cell
cell.myLabel.text = self.items[indexPath.row] // The row value is the same as the index of the desired text within the array.
cell.backgroundColor = UIColor(rgb: 0xF444444)
cell.setStatusbar(completedTasks: 5, totalTasks: 25) //!!! HERE !!!
return cell
}
Can someone help me find the problem why the statusbar uiview doesn’t change its with from 5 to 80?
Thank you!

Adding images to table view cell problem using stack views

in my prototype cell I have a horizontal stack view and I connected that to my UITableViewCell and I defined an update function In my UITableViewCell that adds multiple images to the stack view and I called the function In TableViewController cellForRowAt but nothing nothing happens.
//inside UITableViewCell
#IBOutlet weak var lineStack: UIStackView!
func update(_ imageName: String){
let numberOfCopies = Int(deviceWidth/50)
let startX = (Int(deviceWidth) - (numberOfCopies*50))/2
xValue = xValue + startX
let image = UIImage(named: imageName)
for _ in 1...Int(numberOfCopies) {
xValue = xValue + heightValue
let imageView = UIImageView(image: image)
imageView.frame = CGRect(x: xValue , y: 0, width: widthValue, height: heightValue)
lineStack.addSubview(imageView)
}
}
//inside TableViewController
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Line", for: indexPath) as! LineTableViewCell
let imageName = imagesName[indexPath.row]
cell.update(imageName)
return cell
}
Your post indicates you want your images to be 50 x 50 points... use auto-layout constraints with .addArrangedSubview():
class TestCell: UITableViewCell {
#IBOutlet var lineStack: UIStackView!
func update(_ imageName: String) {
let numberOfCopies = Int(deviceWidth/50)
let image = UIImage(named: imageName)
for _ in 1...Int(numberOfCopies) {
let imageView = UIImageView(image: image)
imageView.translatesAutoresizingMaskIntoConstraints = false
imageView.widthAnchor.constraint(equalToConstant: 50).isActive = true
imageView.heightAnchor.constraint(equalTo: imageView.widthAnchor).isActive = true
lineStack.addArrangedSubview(imageView)
}
}
}
EDIT Sample result, with stack view centered horizontally:
Stack view is set to:
Axis: Horizontal
Alignment: Fill
Distribution: Fill
Spacing: 0
and this is the layout with constraints:
Note that the stack view has a Width constraint of 10, but its Priority is 250 ... that allows the stack view to stretch horizontally, while keeping IB satisfied with the constraints.
For the line:
lineStack.addSubview(imageView)
Instead of addSubview() you need to do it with addArrangedSubview(). The former is the regular way of adding a subview to a view, whereas the latter is specifically for UIStackView and tells the view to insert it properly.

Trying to put a badge on an UIImageView

I built a UIImageView extension to use the image in it and add a badge to it. But i really cannot get it right because after that i'm rounding the UIImageView corners radius. What i want is to show the badge on top of the UIImageView corner of the image.
This is how i do it:
public extension UIImageView {
func addBadgeRightBottom (withBadge badge: UIImage) {
if self.image != nil {
UIGraphicsBeginImageContextWithOptions(self.frame.size, false, 0.0)
self.image?.draw(in: CGRect(x: 0, y: 0, width: (self.frame.size.width), height: (self.frame.size.height)))
badge.draw(in: CGRect(x: (self.frame.size.width) - badge.size.width, y: (self.frame.size.height) - badge.size.height, width: badge.size.width, height: badge.size.height))
let image = UIGraphicsGetImageFromCurrentImageContext()
DispatchQueue.main.async() { () -> Void in
self.image = image
}
UIGraphicsEndImageContext()
}
}
}
This is what i'm getting:
What i really want to have:
At last i did it by creating a new UIImageView of the badge UIImage size in the same position i wanted on the other circular UIImageView and added it as a subview to my UICollectionViewCell because these profile picture images are in a UICollectionViewCell
Unfortunately i didn't do it as an extension after all.
Here is how i did it.
After initializing a UIImageView in the CelebCollectionViewCell class
var celebBadgeImageView = UIImageView()
I initialized the CGRect of the badge and added it as a subview
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell: CelebCollectionViewCell = collectionView.dequeueReusableCell(withReuseIdentifier: "celebCell", for: indexPath) as! CelebCollectionViewCell
cell.celebBadgeImageView.frame = CGRect(x: cell.celebImage.frame.size.width - (#imageLiteral(resourceName: "verifiedCelebrity").size.width/2), y: cell.celebImage.frame.size.height - #imageLiteral(resourceName: "verifiedCelebrity").size.height, width: #imageLiteral(resourceName: "verifiedCelebrity").size.width, height: #imageLiteral(resourceName: "verifiedCelebrity").size.height)
cell.celebBadgeImageView.image = #imageLiteral(resourceName: "verifiedCelebrity")
cell.addSubview(cell.celebBadgeImageView)
cell.celebImage.image = #imageLiteral(resourceName: "profile_user.jpg")
cell.celebImage.circularView()
return cell
}

UICollectionView cell views overlapping

I have cells overlapping like so:
my cellForItemAtIndexPath is as such:
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCellWithReuseIdentifier("Cell", forIndexPath: indexPath) as UICollectionViewCell
cell.backgroundColor = UIColor(red: 27.0/255.0, green: 38.0/255.0, blue: 52.0/255.0, alpha: 1.0)
let textFrame = CGRect(x: 0, y: cell.frame.height * 0.30, width: cell.frame.width, height: cell.frame.height)
var textLabel: UILabel! = UILabel(frame: textFrame)
textLabel.font = UIFont(name:"Helvetica-Light", size: 14.0)
textLabel.textAlignment = .Center
textLabel.textColor = UIColor.whiteColor()
println(categoryArray[indexPath.row].category)
textLabel.text = categoryArray[indexPath.row].category
var cellImage = UIImage(named: categoryArray[indexPath.row].catImage)//Array(Array(model.categories.values)[cellCount])[1]
let imageSize = cell.frame.height * 0.45
let imageView = UIImageView(image: cellImage as UIImage?)
imageView.frame = CGRect(x: (cell.frame.width / 2) - (imageSize / 2), y:cell.frame.height * 0.15, width: imageSize, height: imageSize)
var bottomBorder: UIView = UIView(frame:CGRectMake(0, cell.frame.height - 1.0, cell.frame.width, 5));
//bottomBorder.backgroundColor = UIColor(rgba: Array(Array(model.categories.values)[cellCount])[0] as String)
bottomBorder.backgroundColor = UIColor(rgba: "#A64259")
cell.addSubview(imageView)
cell.addSubview(bottomBorder)
cell.addSubview(textLabel)
cellCount++
return cell
}
I understand that it reuses the cells, great idea...except how do I prevent the cell text from overlapping?
EDIT - POTENTIAL SOLUTION #1
Since these subviews were continually being modified I figured, what if I just dumped them and created new ones so I used:
for view in cell.subviews {
view.removeFromSuperview()
}
And that seemed to do the trick. I suspect this has a little more overhead than just modifying the values of the specific elements in the subviews. Will investigate further.
The reason it's happening is because the cells are being reused and you end up adding the image as a subview multiple times to the same UICollectionViewCell object. You can make a custom class that extends UICollectionViewCell so that you can hold onto the imageView that you add.
class ImageCell : UICollectionViewCell {
private(set) var imageView : UIImageView?
private(set) var textLabel : UILabel?
func setImage(image: UIImage?) {
if self.imageView == nil {
let imageSize = cell.frame.height * 0.45
self.imageView = UIImageView()
self.imageView.frame = CGRect(x: (self.frame.width / 2) - (imageSize / 2), y:self.frame.height * 0.15, width: imageSize, height: imageSize)
self.addSubview(imageView!)
}
imageView!.image = image
}
func setLabel(text: String) {
if self.textLabel == nil {
let textFrame = CGRect(x: 0, y: self.frame.height * 0.30, width: self.frame.width, height: self.frame.height)
self.textLabel = UILabel(frame: textFrame)
textLabel.font = UIFont(name:"Helvetica-Light", size: 14.0)
textLabel.textAlignment = .Center
textLabel.textColor = UIColor.whiteColor()
}
textLabel.text = text
}
}
Then in your cellForItemAtIndexPath:
let cell = collectionView.dequeueReusableCellWithReuseIdentifier("Cell", forIndexPath: indexPath) as ImageCell
var cellImage = UIImage(named: categoryArray[indexPath.row].catImage)
cell.setImage(cellImage)
cell.setLabel(categoryArray[indexPath.row].category)
Obviously you would have to customize it to get the same layout, but that should get you started.
Well, since it reuses the cell, and since you are adding subviews to the cell on every call, you will end up with multiple overlapping views in the same cell!
Instead, you may want to add the subviews only once, tag them, then on getting called to provide a cell dequeue one, retrieve the subviews using their tags, and set their properties as needed.