Accessing collectionView cells visible/invisible - swift3

I got a function to flip my collection view cells, which is working fine. My problem is that I want to flip all the cells and not just this one visible cell, so when I swipe to next cell it will be flipped.
This is what I am using to flip the one visible cell only. Any help in the right direction would be appreciated.
func flipAction() {
let visibleRect = CGRect(origin: mainCollecView.contentOffset, size: mainCollecView.bounds.size)
let visiblePoint = CGPoint(x: visibleRect.midX, y: visibleRect.midY)
let visibleIndexPath = mainCollecView.indexPathForItem(at: visiblePoint)
let cell = mainCollecView.cellForItem(at: visibleIndexPath!) as! MainCollectionViewCell
if cell.isFlipped == false {
//Flip card
cell.flip()
cell.isFlipped = true
flipBtn.setImage(UIImage(named: "reversed"), for: .normal)
} else {
// Flip the card back
flipBtn.setImage(UIImage(named: "Calendar"), for: .normal)
cell.flipBack()
cell.isFlipped = false
}
}

You can use different approaches. But if you want all to be flipped, use a variable in you Class, like
var isFlipped:Bool = false
Inside your function now:
flipAction() {
isfliped = !isfliped
mycollection.reload() //where mycollection is yours
}
Then inside your
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell
{
if isfliped == false {
// your code for cell when is not flipped
} else {
//your code for cell when is flipped
}
This way all cells will be flipped or not at once, not depending if they are visible or not.

Related

How to generate annotations on a certain zoom and only those close to where user zoomed in?

My app requests JSON data (latitude, longitude, and other information about a place) and then displays them on a map in a form of clickable annotations. I'm receiving around 30,000 of those, so as you can imagine, the app can get a little "laggy".
The solution I think would fit the app best is to show those annotations only on a certain zoom level (for example when the user zooms so only one city is visible at once, the annotations will show up). Since there's a lot of them, showing all 30,000 would probably crash the app, that's why I also aim at showing just those that are close to where the user zoomed in.
The code below shows immediately all annotations at once at all zoom levels. Is there a way to adapt it to do the things I described above?
struct Map: UIViewRepresentable {
#EnvironmentObject var model: ContentModel
#ObservedObject var data = FetchData()
var locations:[MKPointAnnotation] {
var annotations = [MKPointAnnotation]()
// Loop through all places
for place in data.dataList {
// If the place does have lat and long, create an annotation
if let lat = place.latitude, let long = place.longitude {
// Create an annotation
let a = MKPointAnnotation()
a.coordinate = CLLocationCoordinate2D(latitude: Double(lat)!, longitude: Double(long)!)
a.title = place.address ?? ""
annotations.append(a)
}
}
return annotations
}
func makeUIView(context: Context) -> MKMapView {
let mapView = MKMapView()
mapView.delegate = context.coordinator
// Show user on the map
mapView.showsUserLocation = true
mapView.userTrackingMode = .followWithHeading
return mapView
}
func updateUIView(_ uiView: MKMapView, context: Context) {
// Remove all annotations
uiView.removeAnnotations(uiView.annotations)
// HERE'S WHERE I SHOW THE ANNOTATIONS
uiView.showAnnotations(self.locations, animated: true)
}
static func dismantleUIView(_ uiView: MKMapView, coordinator: ()) {
uiView.removeAnnotations(uiView.annotations)
}
// MARK: Coordinator Class
func makeCoordinator() -> Coordinator {
return Coordinator(map: self)
}
class Coordinator: NSObject, MKMapViewDelegate {
var map: Map
init(map: Map) {
self.map = map
}
func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
// Don't treat user as an annotation
if annotation is MKUserLocation {
return nil
}
// Check for reusable annotations
var annotationView = mapView.dequeueReusableAnnotationView(withIdentifier: Constants.annotationReusedId)
// If none found, create a new one
if annotationView == nil {
annotationView = MKMarkerAnnotationView(annotation: annotation, reuseIdentifier: Constants.annotationReusedId)
annotationView!.canShowCallout = true
annotationView!.rightCalloutAccessoryView = UIButton(type: .detailDisclosure)
} else {
// Carry on with reusable annotation
annotationView!.annotation = annotation
}
return annotationView
}
}
}
Been searching for an answer for a while now and found nothing that worked well. I imagine there's a way to get visible map rect and then condition that in Map struct, but don't know how to do that. Thanks for reading this far!
Your delegate can implement mapView(_:regionDidChangeAnimated:) to be notified when the user finishes a gesture that changes the map's visible region. It can implement mapViewDidChangeVisibleRegion(_:) to be notified while the gesture is happening.
You can get the map's visible region by asking it for its region property. Regarding zoom levels, the region documentation says this:
The region encompasses both the latitude and longitude point on which the map is centered and the span of coordinates to display. The span values provide an implicit zoom value for the map. The larger the displayed area, the lower the amount of zoom. Similarly, the smaller the displayed area, the greater the amount of zoom.
Your updateUIView method recalculates the locations array every time SwiftUI calls it (because locations is a computed property). You should check how often SwiftUI is calling updateUIView and decide whether you need to cache the locations array.
If you want to efficiently find the locations in the visible region, try storing the locations in a quadtree.
Finally figured that out...
The Coordinator class can implement mapView(_:regionDidChangeAnimated:) (as #rob mayoff said) that gets called after the user finishes a gesture that changes the map's visible region. When that happens, annotations on the map and their array are updated. Looks something like this...
func mapView(_ mapView: MKMapView, regionDidChangeAnimated animated: Bool) {
if mapView.region.span.latitudeDelta < <Double that represents zoom> && mapView.region.span.longitudeDelta < <Double that represents zoom> {
mapView.removeAnnotations(mapView.annotations)
mapView.addAnnotations(map.getLocations(center: mapView.region.center))
}
}
... phrases (doubles missing from the if statement) in < > are to be replaced with your own code (the greater the double, the smaller zoom is needed to view the annotations). The array of annotations is updated by a function defined in Map struct and looks like this...
func getLocations(center: CLLocationCoordinate2D) -> [MKPointAnnotation] {
var annotations = [MKPointAnnotation]()
let annotationSpanIndex: Double = model.latlongDelta * 10 * 0.035
// Loop through all places
for place in data.dataList {
// If the place does have lat and long, create an annotation
if let lat = place.latitude, let long = place.longitude {
// Create annotations only for places within a certain region
if Double(lat)! >= center.latitude - annotationSpanIndex && Double(lat)! <= center.latitude + annotationSpanIndex && Double(long)! >= center.longitude - annotationSpanIndex && Double(long)! <= center.longitude + annotationSpanIndex {
// Create an annotation
let a = MKPointAnnotation()
a.coordinate = CLLocationCoordinate2D(latitude: Double(lat)!, longitude: Double(long)!)
a.title = place.adresa ?? ""
annotations.append(a)
}
}
}
return annotations
}
... where annotationSpanIndex determines in how big of a region around the center point will the annotations be shown (greater the index, bigger the region). This region should be ideally slightly larger than the zoom on which the annotations are shown.

How to set an image in imageview swift 3?

So I have an image view titled cashOrCredit and have am trying to set it's image programatically but somehow not able to.
First I set the image like this
cell.cashOrCredit.image = UIImage(named: "cash1.png")
and I get an error saying a separator of "," is needed.
Then I tried it this way
var cashImage: UIImage?
cashImage = "cash1.png"
cell.cashOrCredit.image = cashImage
But I get a THREAD 1 EXC BAD INSTRUCTION error.
I can't seem to understand what is going wrong ?
Here is the error
Updated for Swift 3:
use below simple code, to set the image to UIImageView;
class YourViewControllerName: UIViewController {
var mYourImageViewOutlet: UIImageView?
func addImageToUIImageView{
var yourImage: UIImage = UIImage(named: "Birthday_logo")!
mYourImageViewOutlet.image = yourImage
} // call this function where you want to set image.
}
Note: "Birthday_logo" type of image must be present in your Assets.xcassets of your project.
I attached the screenshot if you want any help please refer it.
****// used anywhere you want to add an image to UIImageView. [Here I used one function & in that function, I write a code to set image to UIImageView]****
Enjoy..!
Try this:
cell.cashOrCredit.image = UIImage(named: "cash1")
and check "cash1.png" image is available in Assets.xcassets or not.
If you get solution, then give upvote to my answer.
Delete ".png":
cell.cashOrCredit.image = UIImage(named: "cash1")
You can also set it all programmatically:
let cellImage = UIImageView(frame: CGRect(x: X, y: Y, width: WIDTH, height: HEIGHT))
cellImage.image = UIImage(named: "cash1")
cell.addSubview(cellImage)
Take Outlet of ImageView
#IBOutlet weak var imgProfile: UIImageView!
Go through the following code which contains will be helpful you to pick image or capture image from the camera.
func choosePicker() {
let alertController = UIAlertController(title: "Select Option", message: nil, preferredStyle: (IS_IPAD ? UIAlertControllerStyle.alert : UIAlertControllerStyle.actionSheet))
let cancelAction = UIAlertAction(title: "Cancel", style: UIAlertActionStyle.cancel, handler: { action -> Void in
})
let gallery = UIAlertAction(title: "From Gallery", style: UIAlertActionStyle.default
, handler: { action -> Void in
self.openPicker(isCamera: false)
})
let camera = UIAlertAction(title: "Take Photo", style: UIAlertActionStyle.default
, handler: { action -> Void in
self.openPicker(isCamera: true)
})
alertController.addAction(gallery)
alertController.addAction(camera)
alertController.addAction(cancelAction)
self.present(alertController, animated: true, completion: nil)
}
func openPicker(isCamera : Bool) {
let picker:UIImagePickerController?=UIImagePickerController()
if isCamera {
picker!.sourceType = UIImagePickerControllerSourceType.camera
} else {
picker!.sourceType = UIImagePickerControllerSourceType.photoLibrary
picker!.allowsEditing = true
}
picker?.delegate = self
if UIDevice.current.userInterfaceIdiom == .phone {
self.present(picker!, animated: true, completion: nil)
}
else {
picker!.modalPresentationStyle = .popover
present(picker!, animated: true, completion: nil)//4
picker!.popoverPresentationController?.sourceView = imgProfile
picker!.popoverPresentationController?.permittedArrowDirections = UIPopoverArrowDirection.up
}
}
// MARK: - UIImagePickerControllerDelegate Methods
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : Any]) {
if let image = info[UIImagePickerControllerOriginalImage] as? UIImage {
self.imgProfile.image = image
}
picker.dismiss(animated: true, completion: nil);
}
func imagePickerControllerDidCancel(_ picker: UIImagePickerController) {
dismiss(animated: true, completion: nil)
}
Call choosePicker method wherever you want to call.
Check if cashOrCredit is uiimageview.
Secondly,
cashImage = UIImage(named: "cash1.png")
If that doesnot work, try
cell?. cashOrCredit.image
check if cell is nil?
In the context of Swift code, EXC_BAD_INSTRUCTION usually means you’ve hit a compiler trap, that is, an undefined instruction inserted into the code by the compiler because of a bug detected at runtime. The most common cause of these are:
failure to unwrap an optional — This can be a forced unwrap (!) or an implicit unwrap (accessing an implicitly unwrapped optional that’s nil).
array out of bounds
a failed forced cast (as!), either because the value was a nil optional or because the value was of the wrong type

Creating UIcollectionView with Waterfall layout and self sizing cell

I am trying to implement waterfallLayout with a self sizing cell that should work with iOS >= 9.0
Something like pintrest collectionViewLayout not the transition but the difference is that i have a fixed ratio of image but fields below is dynamic which i used UIStackView.
The self sizing is working fine but my problem is in changing the cell origin to be always = 15 PX between cells.
so when i tried FlowLayout without any customisation everything is working fine except for the margins between cells is not correct but the scrolling is working well without any performance issue for iOS >= 9.0
After trying CustomizingFlowLayout the performance is so bad in iOS 10 things is not working while in iOS 9 works fine. i know that apple did some improvement with the UICollectionView but with me i don't see that improvement.
My Cell:
override func preferredLayoutAttributesFitting(_ layoutAttributes: UICollectionViewLayoutAttributes) -> UICollectionViewLayoutAttributes {
self.setNeedsLayout()
self.layoutIfNeeded()
layoutAttributes.bounds.size.height = systemLayoutSizeFitting(layoutAttributes.size, withHorizontalFittingPriority: UILayoutPriorityRequired, verticalFittingPriority: UILayoutPriorityFittingSizeLevel).height
return layoutAttributes
}
My controller:
ViewDidLoad {
layoutView = collectionView.collectionViewLayout as! CustomLayout
if #available(iOS 10.0, *) {
self.collectionView.isPrefetchingEnabled = false
}
layoutView.estimatedItemSize = CGSize(width: layoutView.cellWidth, height: 200)
}
extension RecipesCollectionWithSortBar: UICollectionViewDataSource {
func numberOfSections(in collectionView: UICollectionView) -> Int {
return 1
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return array.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: customCell.reuseIdentifier, for: indexPath) as! customCell
return cell
}
extension RecipesCollectionWithSortBar : UICollectionViewDelegateFlowLayout {
func scrollViewDidScroll(_ scrollView: UIScrollView) {
if scrollView.bounds.maxY == scrollView.contentSize.height && !isLoadingMoreRecipes {
//
LoadMore
}
}
My CustomFlowLayout
override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
let attrs = super.layoutAttributesForElements(in: rect)
var attrsCopy = [UICollectionViewLayoutAttributes]()
for element in attrs! where (element.copy() as! UICollectionViewLayoutAttributes).representedElementCategory == .cell
&& element.frame.height != cacheCells[element.indexPath.item]?.frame.height {
if element.indexPath.item == 0 {
element.frame.origin.y = self.sectionInset.top
element.frame.origin.x = self.sectionInset.left
} else if element.indexPath.item == 1 {
element.frame.origin.y = self.sectionInset.top
element.frame.origin.x = cacheCells[0]!.frame.maxX + margin
} else {
let previous = cacheCells[element.indexPath.item - 2]
element.frame.origin.y = previous!.frame.maxY + self.minimumInteritemSpacing
element.frame.origin.x = previous!.frame.origin.x
}
cacheCells[element.indexPath.item] = element
attrsCopy.append(element)
}
let values = cacheCells.values.filter({ $0.frame.intersects(rect) })
return values.map({ $0 })
}
Here i have many problems :
1- i have a huge white space at the end of the collectionView because i changed the cell origin this problem only happens at iOS > 10.
i tried to override the content size
func getContentHeight() -> CGFloat {
if !cacheCells.values.isEmpty {
let attribute = cacheCells.values.max { $0.0.frame.maxY < $0.1.frame.maxY }
return attribute!.frame.maxY + sectionInset.bottom
}
return contHeight
}
override var collectionViewContentSize: CGSize {
return CGSize(width: self.cellWidth * CGFloat(columnCount), height: getContentHeight())
}
for some reason iOS 9 don't like this solution the scroll stops ..
2- Scrolling performance is so bad in iOS 10 and worst in iOS 9.0.
3- when i load more believe it or not it scroll 3 or 4 times and doesn't have more items to show when i print the log (i have 100 item but the collection view show only 70) and sometimes when hard scrolling it remembers that there's some more items and show them. i am not sure what i am doing wrong.
It's been 2 weeks now trying to figure out what's wrong may be having some fresh eye and mind can point on the problem i am having.

Finding the CGRect of a view

I have a view which subclasses a collectionView horizontal. Each cell takes the full width and the full height. Within each cell, I have a button and I'm not able to find its proper location relative to my viewController's main view.
I have tried the following and many other combination but
guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: CellIds.studyCell, for: [0,1]) as? StudyCell else { return nil }
transition.originFrame = self.view.convert(cell.explanationButton.frame, to: self.view.superview)
transition.presenting = true
return transition
}

iOS - How to insert a new To-Do item in a single view controller?

I want to add a new To Do item when i press the add button,but i don't want to switch to another page.
press the add button to add a new row in the table view,input something and press the done button,it will save.
somebody suggests me to save the cells data to Model,but i don't know how to write this.
Who can help me?
import UIKit
import CoreData
class ToDoViewController: UIViewController {
var items: [NSManagedObject] = []
#IBOutlet weak var tableView: UITableView!
#IBAction func addItem(_ sender: UIBarButtonItem) {
//***How to write this code***
}
#IBAction func done(_ sender: UIBarButtonItem) {
guard let appDelegate = UIApplication.shared.delegate as? AppDelegate else {
return
}
let managedContext = appDelegate.persistentContainer.viewContext
let entity = NSEntityDescription.entity(forEntityName: "ToDo", in: managedContext)!
let item = NSManagedObject(entity: entity, insertInto: managedContext)
//***let list = the current textView's text
//how to get the textView's text and assign it to a value.***
item.setValue(list, forKeyPath: "summary")
do {
try managedContext.save()
items.append(item)
} catch let error as NSError {
print("Could not save.\(error),\(error.userInfo)")
}
}
override func viewDidLoad() {
super.viewDidLoad()
tableView.register(UITableViewCell.self,forCellReuseIdentifier: "Cell")
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
guard let appDelegate = UIApplication.shared.delegate as? AppDelegate else {
return
}
let managedContext = appDelegate.persistentContainer.viewContext
let fetchRequest = NSFetchRequest<NSManagedObject>(entityName: "ToDo")
do {
items = try managedContext.fetch(fetchRequest)
} catch let error as NSError {
print("Could not fetch.\(error),\(error.userInfo)")
}
}
}
extension ToDoViewController: UITableViewDataSource{
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return items.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let item = items[indexPath.row]
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath)
let textView = UITextView(frame: CGRect(x: 0, y: 0, width: cell.frame.size.width, height: cell.frame.size.height))
cell.addSubview(textView)
textView.text = item.value(forKey: "summary") as? String
return cell
}
}
Ok so If my understanding is right you need a new row to be added if they create a new entry into your Core Data. So in your viewWillAppear you're doing a fetch. What I think you need is a:
var fetchResultController : NSFetchedResultsController<YourType>!
Then using this fetch controller you want to do the following when fetching:
private func GetToDoEntries(){
if let appDele = UIApplication.shared.deletgate as? AppDelegate{
let givenContext = appDele.persistantContainer.viewContex
let entryFetchRequest : NSFetchRequest<YourType> = YourType.fetchRequest()
let sortDescriptors = NSSortDescriptor(key: "yourEntrySortKey" , ascending: true)
entryFetchRequest.sortDescriptors = [sortDescriptors]
fetchResultController = NSFetchedResultsController(fetchRequest: entryFetchRequest, managedObjectContext: givenContext, sectionNameKeyPath: nil, cacheName: nil)
fetchResultController.delegate = self
do{
//Gets fetched data based on our criteria
try fetchResultController.performFetch()
if let fetchedEntries = fetchResultController.fetchedObjects{
items = fetchedEntries as? WhateverToCastTo
}
}catch{
print("Error when trying to find entries")
}
}
}
First I'm sorry but I've just written this here is stackOverflow. So what you're doing is using a fetch result controller instead of a traditional search. You are required to have the sort descriptor as well and you can try to get the results and cast them to your items or as a NSManagedObject.
Now we're not done though. Your script needs to inherit from some behaviour. At the top of your class
class ToDoViewController : UIViewController, NSFetchedResultsControllerDelegate
You need the delegate as you can see in the first block of code because we're assigning it. Now we're almost there. You just need some methods to update the table for you and these come with the delegate we just inherited from.
//Allows the fetch controller to update
func controllerWillChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {
tableView.beginUpdates()
}
//Allows new additions to be added to the table
func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, didChange anObject: Any, at indexPath: IndexPath?, for type: NSFetchedResultsChangeType, newIndexPath: IndexPath?) {
switch type{
case .insert:
if let _newIndexPath = newIndexPath{
tableView.insertRows(at: [_newIndexPath], with: .fade)
}
case .update:
if let index = indexPath{
tableView.reloadRows(at: [index], with: .fade)
}
default:
budgetEntryTable.reloadData()
}
if let fetchedObjects = controller.fetchedObjects{
items = fetchedObjects as! [NSManagedObject (Or your cast type)]
budgetEntryTable.reloadData()
}
}
//Ends the table adding
func controllerDidChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {
tableView.endUpdates()
}
So there are 3 methods here. The first and second are very self explanatory. They begin and end the updates on your tableView. I'd also recommend that you change the name of your tableView to something other than "tableView" just for clarity.
The method in the middle uses a switch. My example is missing the "Move" and "Delete" options as I didn't required them in my project but you can add them to the switch statement.
The insert is checking the newIndexPath to see if there is one. If so then we add an array of the amount of rows required at the newIndexPath.
The update just checks the current index paths and then reloads the data on them incase you updated your data model.
I hope this is what you were looking for. Good luck! I'll try and help more if you need it but that should get you started.