I've been researching on how to embed my own custom decimal keyboard into a UIView. i've found below image, which i would like to recreate. However i'm not sure on how and what is the best way to get started with such? is it just to create multiple UIButton and then handle it in a method or is there a smart way to recreate such ?
A collectionView is a great way to do this. Create a new storyboard, put a UICollectionViewController in there. Then create a UICollectionViewCell with an UILabel (for digits and a dot) and an UIImage (for the delete button).
Here's my UICollectionViewController code for this:
import UIKit
protocol KeypadDelegate: class {
func did(tapOnKeypad key: KeypadKey)
}
enum KeypadKey {
case number (value: Int)
case backspace
}
class KeypadViewController: UICollectionViewController, UICollectionViewDelegateFlowLayout {
// MARK: - Properties.
weak var delegate: KeypadDelegate?
// MARK: - Lifecycle.
override func viewDidLoad() {
super.viewDidLoad()
collectionView?.register(cellReuseID: KeypadCVCell.reuseID)
}
// MARK: - UICollectionView DataSource.
override func numberOfSections(in collectionView: UICollectionView) -> Int {
return 1
}
override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 12
}
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "KeypadCVCell", for: indexPath) as! KeypadCVCell
switch indexPath.row {
case 0...8:
cell.configure(number: String(indexPath.row + 1))
case 9:
cell.setBlank()
case 10:
cell.configure(number: "0")
case 11:
let image = UIImage(named: "btn_keypad_backspace")
cell.configure(image: image)
default:
break
}
return cell
}
// MARK: - UICollectionView Delegate.
override func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
switch indexPath.row {
case 0...8:
let key: KeypadKey = .number(value: indexPath.row + 1)
delegate?.did(tapOnKeypad: key)
case 9:
break
case 10:
let key: KeypadKey = .number(value: 0)
delegate?.did(tapOnKeypad: key)
case 11:
delegate?.did(tapOnKeypad: .backspace)
default:
break
}
}
// MARK: - UICollectionView Delegate FlowLayout.
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let width = collectionView.bounds.width / 3
let height = collectionView.bounds.height / 4
return CGSize(width: width, height: height)
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat {
return 0
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumInteritemSpacingForSectionAt section: Int) -> CGFloat {
return 0
}
}
class KeypadCVCell: UICollectionViewCell {
// MARK: - Outlets.
#IBOutlet weak var numberLabel: UILabel!
#IBOutlet weak var backspaceImageView: UIImageView!
// MARK: - Configuration.
func configure(number: String) {
self.numberLabel.text = number
self.backspaceImageView.isHidden = true
}
func configure(image: UIImage?) {
self.numberLabel.isHidden = true
self.backspaceImageView.image = image
}
func setBlank() {
self.numberLabel.isHidden = true
self.backspaceImageView.isHidden = true
}
}
Create a single UIButton action outlet, connect all the buttons to it, check the senders title value, convert it to Int, if not possible it's the comma or if title text is empty it's the backspace.Or create a separate function for the backspace. That would be a relatively easy, clutter free way to do it and wouldn't take much more than a dozen lines of code.
Related
I have problem with lottie animations, I have some kind of onboarding on my app and what I would like to achive is to everytime view in collectionview is changed, to start my animation, I have 4 pages and 4 different lottie animations. Currently if I call animation.play() function, once app is started, all of my animations are played at the same time, so once I get to my last page, animation is over. And I want my lottie to be played only once, when view is shown.
This is my cell
class IntroductionCollectionViewCell: UICollectionViewCell {
#IBOutlet var title: UILabel!
#IBOutlet var subtitleDescription: UILabel!
#IBOutlet var animationView: AnimationView!
override func awakeFromNib() {
super.awakeFromNib()
}
public func configure(with data: IntroInformations) {
let animation = Animation.named(data.animationName)
title.text = data.title
subtitleDescription.text = data.description
animationView.animation = animation
}
static func nib() -> UINib {
return UINib(nibName: "IntroductionCollectionViewCell", bundle: nil)
}
}
This is how my collection view is set up
extension IntroductionViewController {
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat {
return 0
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
pages.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "IntroductionCollectionViewCell", for: indexPath) as! IntroductionCollectionViewCell
cell.configure(with: pages[indexPath.row])
cell.animationView.loopMode = .loop
cell.animationView.play()
return cell
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: view.frame.width, height: view.frame.height)
}
func scrollViewDidScroll(_ scrollView: UIScrollView) {
let scrollPos = scrollView.contentOffset.x / view.frame.width
self.pageControl.currentPage = Int(floorf(Float(scrollPos)))
let n = pageControl.numberOfPages
if self.pageControl.currentPage == n - 1 {
continueButton.isHidden = false
} else {
continueButton.isHidden = true
}
}
}
Thanks in advance!!
You can use the collectionViewDelegate willDisplay and didEndDisplaying methods to start/stop the animations. And not when configuring the cell. - https://developer.apple.com/documentation/uikit/uicollectionviewdelegate
If you want the animation to run only once dont use the loop option.
if let cell = cell as? IntroductionCollectionViewCell {
cell.animationView.loopMode = .playOnce
cell.animationView.play()
}
this is the answer, I need to check is cell once is loaded my cell where lottie animation is
I am showing some images in the collection view cells . As of now I am using a simple collection view which shows the items and it is currently showing two items per row . The following code is given below:
import UIKit
import MBProgressHUD
class HotViewController: BaseViewController {
// MARK: - IBOutlets
#IBOutlet var collectionView: UICollectionView!
#IBOutlet weak var filterSwitch: UISwitch!
#IBOutlet weak var toolbarHeightConstraint: NSLayoutConstraint!
#IBOutlet weak var toolbarView: UIView!
#IBOutlet weak var messageLabel: UILabel!
// MARK: - Properties
let hotViewModel = HotViewModel()
var hotPhotos = [ImageMeta]()
var filteredPhotos = [ImageMeta]()
// MARK: - ViewLifeCycle
override func viewDidLoad() {
super.viewDidLoad()
setupViewModel()
}
// MARK: - Methods
func setupViewModel() {
MBProgressHUD.showAdded(to: self.view, animated: true)
hotViewModel.getHotPhotos()
hotViewModel.getPhotoListDidSucess = { [weak self] list in
guard let strongSelf = self else {return}
strongSelf.hotPhotos = list
strongSelf.filteredPhotos = list.filter{$0.in_most_viral == true}
DispatchQueue.main.async {
MBProgressHUD.hide(for: strongSelf.view, animated: true)
strongSelf.collectionView.reloadData()
}
}
hotViewModel.getPhotoListDidFailed = { [weak self] message in
print("message \(message)")
guard let strongSelf = self else {return}
DispatchQueue.main.async {
MBProgressHUD.hide(for: strongSelf.view, animated: true)
}
}
}
// MARK: - Actions
#IBAction func switchValueDidChanged(_ sender: Any) {
self.collectionView.reloadData()
}
#IBAction func aboutButtonClicked(_ sender: UIButton) {
showPopUp()
}
}
// MARK: - CollectionViewDataSource
extension HotViewController: UICollectionViewDataSource,UICollectionViewDelegate {
func numberOfSections(in collectionView: UICollectionView) -> Int {
return 1
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
if filterSwitch.isOn && filteredPhotos.count == 0 {
messageLabel.isHidden = false
}
else{
messageLabel.isHidden = true
}
return (filterSwitch.isOn ? filteredPhotos.count : hotPhotos.count)
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let photo = filterSwitch.isOn ? filteredPhotos[indexPath.row] : hotPhotos[indexPath.row]
let cell : PhotoListCell = collectionView.dequeueReusableCell(for: indexPath)
cell.configureCell(with: photo)
return cell
}
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
let photo = filterSwitch.isOn ? filteredPhotos[indexPath.row] : hotPhotos[indexPath.row]
showDetailedPage(metaData: photo)
}
}
// MARK: - UICollectionViewDelegateFlowLayout
extension HotViewController: UICollectionViewDelegateFlowLayout {
func collectionView(_ collectionView: UICollectionView,
layout collectionViewLayout: UICollectionViewLayout,
minimumInteritemSpacingForSectionAt section: Int) -> CGFloat {
return CGFloat(interitemSpacing)
}
func collectionView(_ collectionView: UICollectionView, layout
collectionViewLayout: UICollectionViewLayout,
minimumLineSpacingForSectionAt section: Int) -> CGFloat {
return CGFloat(lineSpacing)
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return getItemSize()
}
}
I am getting the following result as:
So in this image you can see 1 and 2 marked in red . What I want is
A) on pressing 1 the number of items per row should become 1
instead of 2
B) on pressing 2 the cells should become staggered
instead of perfect square .
Is it possible to achieve using the same collection view ?How can I achieve it using the same collection view ?
Here is an example of staggered collection view :
How can I switch to staggered view , list and regular view on button clicks?
is it possible to add flags and when you click you just reload the collectionView.
and do all the code in the delegates of the collection view ...
I have been working on a weather app and been using UICollectionView to display weather data.
Whenever I open another view controller and return back to the UICollectionView's View controller, I get duplicate cells.
Here is the code.
I use Alamofire to make api requests, append the json result to a local string and then assign it to the cell's text labels.
class VaanizhaiViewController: UICollectionViewController {
// MARK: - flow layout
let columns : CGFloat = 2.0
let inset : CGFloat = 3.0
let spacing : CGFloat = 3.0
let lineSpacing : CGFloat = 3.0
var isRandom : Bool = false
// MARK: - Variables
var apiKey: String = "55742b737e883a939913f2c90ee11ec0"
var country : String = ""
var zipCode : String = ""
var json : JSON = JSON.null
var cityNames: [String] = []
var temperature: [Int] = []
var weather: [String] = []
// MARK: - Actions
// MARK: - view did load
override func viewDidLoad() {
super.viewDidLoad()
let layout = BouncyLayout()
self.collectionView?.setCollectionViewLayout(layout, animated: true)
self.collectionView?.reloadData()
}
override func viewDidAppear(_ animated: Bool) {
for i in 0...zip.count-1{
parseURL(zipCode: "\(zip[i])", country: "us")
}
}
// MARK: - Functions
func parseURL(zipCode: String, country: String){
let url = "http://api.openweathermap.org/data/2.5/weather?zip=\(zipCode),\(country)&APPID=\(apiKey)&units=metric"
requestWeatherData(link: url)
}
func requestWeatherData(link: String){
Alamofire.request(link).responseJSON{ response in
if let value = response.result.value{
self.json = JSON(value)
self.cityNames.append(self.json["name"].string!)
let cTemp = ((self.json["main"]["temp"].double)!)
self.temperature.append(Int(cTemp))
let cWeather = self.json["weather"][0]["main"].string!
self.weather.append(cWeather)
self.collectionView?.reloadData()
}
}
}
// MARK: UICollectionViewDataSource
override func numberOfSections(in collectionView: UICollectionView) -> Int {
return 1
}
override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return self.cityNames.count
}
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "weatherCell", for: indexPath) as! WeatherViewCell
if !self.cityNames.isEmpty {
cell.cityLabel.text = self.cityNames[indexPath.row]
cell.tempLabel.text = String (self.temperature[indexPath.row])
cell.weatherLabel.text = self.weather[indexPath.row]
cell.backgroundColor = UIColor.brown
}
return cell
}
// MARK: - UICollectionViewDelegateFlowLayout
extension VaanizhaiViewController: UICollectionViewDelegateFlowLayout{
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let width = Int ((CGFloat(collectionView.frame.width) / columns) - (inset + spacing))
return CGSize(width: width, height: width)
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets {
return UIEdgeInsets(top: CGFloat(inset), left: CGFloat(inset), bottom: CGFloat(inset), right: CGFloat(inset))
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumInteritemSpacingForSectionAt section: Int) -> CGFloat {
return spacing
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat {
return lineSpacing
}
}
Your viewDidAppear will be called every time you visit the view controller, and your viewDidLoad will only get called once, when it's created.
Therefore, you should probably switch your call to self.collectionView?.reloadData() to the viewDidAppear and your call to parseURL to the viewDidLoad
if you need to update your weather data (on a refresh, for example), then you need to restructure your requestWeatherData to stop appending to your arrays, and replace instead.
This is what I am able to make through a tutorial
This is the Screenshot of the app
But I have to make 1st and every 3rd Cell to be full width. How I supposed to do it?
The code is this
class MainViewController: UICollectionViewController
{
// data source
let publishers = Publishers()
private let leftAndRightPaddings: CGFloat = 32.0
private let numberOfItemsPerRow: CGFloat = 2.0
private let heigthAdjustment: CGFloat = 100
// MARK: - View controller life cycle
override func viewDidLoad() {
super.viewDidLoad()
let width = (CGRectGetWidth(collectionView!.frame) - leftAndRightPaddings) / numberOfItemsPerRow
let layout = collectionViewLayout as! UICollectionViewFlowLayout
layout.itemSize = CGSizeMake(width, heigthAdjustment)
print(width.description)
}
// MARK: - UICollectionViewDataSource
override func numberOfSectionsInCollectionView(collectionView: UICollectionView) -> Int {
return 1
}
override func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return publishers.numberOfPublishers
}
private struct Storyboard
{
static let CellIdentifier = "PublisherCell"
}
override func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell
{
let cell = collectionView.dequeueReusableCellWithReuseIdentifier(Storyboard.CellIdentifier, forIndexPath: indexPath) as UICollectionViewCell
return cell}
}
You need to implement this func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAtIndexPath indexPath: NSIndexPath) -> CGSize method in your UICollectionViewDelegateFlowLayout
My UICollectionViewCell doesn't look nice even I implement autolayout and some setting in UICollectionViewCell inspector.
My setting is like this
My UICollectionViewCell autolayout
My ImageView autolayout
My code
PopularViewController.swift
import UIKit
import Alamofire
class PopularViewController: UIViewController,UICollectionViewDataSource, UICollectionViewDelegate, UICollectionViewDelegateFlowLayout {
var users: [JSON] = []
#IBOutlet var collectionView: UICollectionView!
func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
//#warning Incomplete method implementation -- Return the number of items in the section
return users.count
}
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCellWithReuseIdentifier("UserCell", forIndexPath: indexPath) as! PopularCollectionViewCell
cell.user = self.users[indexPath.row]
return cell
}
override func viewDidLoad() {
super.viewDidLoad()
Alamofire.request(.GET, "http://128.199.160.213/datetick/users.json").responseJSON { (request, response, json, error) in
if json != nil {
var jsonObj = JSON(json!)
if let data = jsonObj["users"].arrayValue as [JSON]?{
self.users = data
self.collectionView.reloadData()
}
}
}
}
}
PopularCollectionViewCell.swift
class PopularCollectionViewCell: UICollectionViewCell {
#IBOutlet weak var profilePicture:UIImageView!
var user:JSON?{
didSet{
self.setupUser()
}
}
func setupUser(){
if let urlString = self.user?["profilePhoto"]{
let url = NSURL(string: urlString.stringValue)
self.profilePicture.hnk_setImageFromURL(url!)
}
}
}
My output
I know its late to reply but it might be useful for others.
You can reduce the size using UICollectionViewDelegateFlowLayout. You have already inherited this class in your PopularViewController
Use Following function to do so
//To Show 3 items per row
func collectionView(collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAtIndexPath indexPath: NSIndexPath) -> CGSize {
return CGSize(width: (collectionView.frame.size.width - 3)/3, height: 110)
}
func collectionView(collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumInteritemSpacingForSectionAtIndex section: Int) -> CGFloat {
return 1
}
func collectionView(collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAtIndex section: Int) -> CGFloat {
return 1
}