I've created one storyboard with name Main1.storyboard and this storyboard have backend file with ViewController.swift name. All projects im created with Swift 3.0 . This storyboard is for reading barcode, i want to make after reading barcode to open homeViewController.xib - homeViewController.swift file and to send this barcode result. HomeViewController class have Webview components. For me is important just to open homeViewController.xib - homeViewController.swift file after reading barcode and send barcode result. My homeViewController.swift and ViewController.swift class is as given bellow:
homeViewController.swift:
import UIKit
import SystemConfiguration
import DrawerController
import SVProgressHUD
class homeViewController: UIViewController , UIWebViewDelegate,UIScrollViewDelegate {
var webView: UIWebView!
var text:String = "http://www.google.com"
var barcode:String = ""
override func viewDidLoad() {
super.viewDidLoad()
// setup title
self.title = "My App"
// setup NavigationBar Color
self.navigationController?.navigationBar.barTintColor = navigationBarColor
let ud: UserDefaults = UserDefaults.standard
let data: NSData? = ud.object(forKey: "cookie") as? NSData
if let cookie = data {
let datas: NSArray? = NSKeyedUnarchiver.unarchiveObject(with: cookie as Data) as? NSArray
if let cookies = datas {
for c in cookies as! [HTTPCookie] {
HTTPCookieStorage.shared.setCookie(c)
}
}
}
// setup side Bar
if(sideBarPosition == "left"){
self.setupLeftMenuButton()
}
else{
self.setupRightMenuButton()
}
// setup WebView
webView = UIWebView(frame: CGRect(x: 0, y: 0, width: UIScreen.main.bounds.width, height: UIScreen.main.bounds.height-64))
self.view.addSubview(webView)
webView.delegate = self
webView.scrollView.delegate = self
webView.isUserInteractionEnabled = false
SVProgressHUD.show()
if (typeOfData == "html"){
// load from html file
let myfile = Bundle.main.url(forResource: nameFileHtml, withExtension: "html")
let requestObj = NSURLRequest(url: myfile!)
webView.loadRequest(requestObj as URLRequest)
}
else{
// load website data
if(connectedToNetwork() == false){
SVProgressHUD.showError(withStatus: "No Internet Connection")
}
else{
webView.loadRequest(URLRequest(url: URL(string: text)!))
}
}
}
// setup left menu button icon
func setupLeftMenuButton() {
let button = UIButton.init(type: .custom)
button.setImage(UIImage.init(named: "menuleft.png")?.withRenderingMode(.alwaysTemplate), for: UIControlState.normal)
button.addTarget(self, action:#selector(leftDrawerButtonPress), for: UIControlEvents.touchUpInside)
button.frame = CGRect.init(x: 0, y: 0, width: 30, height: 17) //CGRectMake(0, 0, 30, 30)
button.imageView?.tintColor = iconColor
let barButton = UIBarButtonItem.init(customView: button)
self.navigationItem.leftBarButtonItem = barButton
}
// setup left menu button action
func leftDrawerButtonPress(_ sender: AnyObject?) {
self.evo_drawerController?.toggleDrawerSide(.left, animated: true, completion: nil)
}
// setup right menu button icon
func setupRightMenuButton() {
let button = UIButton.init(type: .custom)
button.setImage(UIImage.init(named: "menuright.png")?.withRenderingMode(.alwaysTemplate), for: UIControlState.normal)
button.addTarget(self, action:#selector(rightDrawerButtonPress), for: UIControlEvents.touchUpInside)
button.frame = CGRect.init(x: 0, y: 0, width: 30, height: 17) //CGRectMake(0, 0, 30, 30)
button.imageView?.tintColor = iconColor
let barButton = UIBarButtonItem.init(customView: button)
self.navigationItem.rightBarButtonItem = barButton
}
// setup right menu button action
func rightDrawerButtonPress(_ sender: AnyObject?) {
self.evo_drawerController?.toggleDrawerSide(.right, animated: true, completion: nil)
}
// setup webView option
func webViewDidStartLoad(_ webView: UIWebView)
{
SVProgressHUD.show()
}
func webViewDidFinishLoad(_ webView: UIWebView)
{
if( barcode != "")
{
self.webView.stringByEvaluatingJavaScript(from:
"var element = document.getElementById('input-password');"
+ "element.value ="+barcode+";" +
"")
}
let cookieJar: HTTPCookieStorage = HTTPCookieStorage.shared
let data: NSData = NSKeyedArchiver.archivedData(withRootObject: cookieJar.cookies) as NSData
let ud: UserDefaults = UserDefaults.standard
ud.set(data, forKey: "cookie")
SVProgressHUD.dismiss()
webView.isUserInteractionEnabled = true
let aLabel = UIButton()
self.view.addSubview(aLabel)
aLabel.widthAnchor.constraint(equalToConstant: 100.0).isActive = true
aLabel.heightAnchor.constraint(equalToConstant: 123.0).isActive = true
aLabel.setImage(UIImage(named: "photo-camera.png"), for: .normal)
aLabel.translatesAutoresizingMaskIntoConstraints = false
let horizontalConstraint = NSLayoutConstraint(item: aLabel, attribute: NSLayoutAttribute.trailing, relatedBy: NSLayoutRelation.equal, toItem: view, attribute:NSLayoutAttribute.trailing, multiplier: 1, constant: 0)
let verticalConstraint = NSLayoutConstraint(item: aLabel, attribute: NSLayoutAttribute.bottom, relatedBy: NSLayoutRelation.equal, toItem: view, attribute: NSLayoutAttribute.bottom, multiplier: 1, constant: 0)
aLabel.addTarget(self, action: #selector(buttonAction), for: .touchUpInside)
NSLayoutConstraint.activate([horizontalConstraint, verticalConstraint])
self.view.layoutIfNeeded()
}
#objc func buttonAction(sender: UIButton!) {
let storyBoard: UIStoryboard = UIStoryboard(name: "Main1", bundle: nil)
let balanceViewController = storyBoard.instantiateViewController(withIdentifier: "barcode") as! ViewController
self.present(balanceViewController, animated: true, completion: nil)
}
func btnclicked(sender: UIButton!)
{
let alert = UIAlertController(title: "Alert", message: "unwraped.stringValue", preferredStyle: UIAlertControllerStyle.alert)
alert.addAction(UIAlertAction(title: "Click", style: UIAlertActionStyle.default, handler: nil))
self.present(alert, animated: true, completion: nil)
}
func scrollViewWillEndDragging(_ scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer<CGPoint>) {
if (scrollView.contentOffset.y < 0){
self.webView.reload()
}
}
// check connection
func connectedToNetwork() -> Bool {
var zeroAddress = sockaddr_in()
zeroAddress.sin_len = UInt8(MemoryLayout<sockaddr_in>.size)
zeroAddress.sin_family = sa_family_t(AF_INET)
guard let defaultRouteReachability = withUnsafePointer(to: &zeroAddress, {
$0.withMemoryRebound(to: sockaddr.self, capacity: 1) {
SCNetworkReachabilityCreateWithAddress(nil, $0)
}
}) else {
return false
}
var flags: SCNetworkReachabilityFlags = []
if !SCNetworkReachabilityGetFlags(defaultRouteReachability, &flags) {
return false
}
let isReachable = flags.contains(.reachable)
let needsConnection = flags.contains(.connectionRequired)
return (isReachable && !needsConnection)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
ViewController.swift
import UIKit
import AVFoundation
class ViewController: UIViewController,UIScrollViewDelegate , UIWebViewDelegate , AVCaptureMetadataOutputObjectsDelegate {
#IBOutlet weak var viewPreview: UIView!
var captureSession: AVCaptureSession?
var videoPreviewLayer: AVCaptureVideoPreviewLayer!
var isReading: Bool = false
var barcoderesult:String=""
#IBOutlet weak var anulo_btn: UIButton!
#IBAction func bnt_cancel(_ sender: Any) {
let vc = homeViewController(nibName: "homeViewController", bundle: nil)
let navigationController = UINavigationController(rootViewController: vc)
self.evo_drawerController?.setCenter(navigationController, withCloseAnimation: true, completion: nil)
self.dismiss(animated: true)
}
// setup left menu button icon
func setupLeftMenuButton() {
let button = UIButton.init(type: .custom)
button.setImage(UIImage.init(named: "menuleft.png")?.withRenderingMode(.alwaysTemplate), for: UIControlState.normal)
button.addTarget(self, action:#selector(leftDrawerButtonPress), for: UIControlEvents.touchUpInside)
button.frame = CGRect.init(x: 0, y: 0, width: 30, height: 17) //CGRectMake(0, 0, 30, 30)
button.imageView?.tintColor = iconColor
let barButton = UIBarButtonItem.init(customView: button)
self.navigationItem.leftBarButtonItem = barButton
}
// setup left menu button action
func leftDrawerButtonPress(_ sender: AnyObject?) {
self.evo_drawerController?.toggleDrawerSide(.left, animated: true, completion: nil)
}
// MARK: - View Life Cycle
override func viewDidLoad() {
super.viewDidLoad()
self.navigationController?.navigationBar.barTintColor = navigationBarColor
// setup title
setupLeftMenuButton()
viewPreview.layer.cornerRadius = 5;
captureSession = nil;
if !isReading {
if (self.startReading()) {
}
}
else {
stopReading()
}
isReading = !isReading
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
// MARK: - IBAction Method
#IBAction func startStopClick(_ sender: UIButton) {
if !isReading {
if (self.startReading()) {
}
}
else {
stopReading()
}
isReading = !isReading
}
// MARK: - Custom Method
func startReading() -> Bool {
let captureDevice = AVCaptureDevice.defaultDevice(withMediaType: AVMediaTypeVideo)
do {
let input = try AVCaptureDeviceInput(device: captureDevice)
captureSession = AVCaptureSession()
captureSession?.addInput(input)
// Do the rest of your work...
} catch let error as NSError {
// Handle any errors
print(error)
return false
}
videoPreviewLayer = AVCaptureVideoPreviewLayer(session: captureSession)
videoPreviewLayer.videoGravity = AVLayerVideoGravityResizeAspectFill
videoPreviewLayer.frame = viewPreview.layer.bounds
viewPreview.layer.addSublayer(videoPreviewLayer)
/* Check for metadata */
let captureMetadataOutput = AVCaptureMetadataOutput()
captureSession?.addOutput(captureMetadataOutput)
captureMetadataOutput.metadataObjectTypes = captureMetadataOutput.availableMetadataObjectTypes
print(captureMetadataOutput.availableMetadataObjectTypes)
captureMetadataOutput.setMetadataObjectsDelegate(self, queue: DispatchQueue.main)
captureSession?.startRunning()
return true
}
func stopReading() {
captureSession?.stopRunning()
captureSession = nil
videoPreviewLayer.removeFromSuperlayer()
}
func captureOutput(_ captureOutput: AVCaptureOutput!, didOutputMetadataObjects metadataObjects: [Any]!, from connection: AVCaptureConnection!) {
for data in metadataObjects {
let metaData = data as! AVMetadataObject
print(metaData.description)
let transformed = videoPreviewLayer?.transformedMetadataObject(for: metaData) as? AVMetadataMachineReadableCodeObject
if let unwraped = transformed {
print(unwraped.stringValue)
// lblString.text = unwraped.stringValue
// btnStartStop.setTitle("Start", for: .normal)
barcoderesult = unwraped.stringValue
let alert = UIAlertController(title: "Alert", message: unwraped.stringValue, preferredStyle: UIAlertControllerStyle.alert)
alert.addAction(UIAlertAction(title: "Click", style: UIAlertActionStyle.default, handler: nil))
self.present(alert, animated: true, completion: nil)
self.performSelector(onMainThread: #selector(stopReading), with: nil, waitUntilDone: false)
isReading = false;
}
}
}
}
It's difficult to answer your question without providing the error you gets when you run or what kind of error is listed in the console.
BTW, i guess you may haven't set the identifier for the viewController in each storyboards.
Related
**Aim: ** To show latest apple calendar type of date picker(inline) in app when user wants to select the date.How it should look
Scenario:**
The user can input multiple entries (maximum 3) the exercise(suryanamaskar). These multiple entries has two functions
1.date-picker and
2.number entry(number of 'suryanamaskar')
When user clicks on the date-picker button. the date picker shows up (.inline). Each entry fetches the post API call that saves the 'suryanamaskar' count entry on server.
What is the issue: I tried to implement the date-picker using and few UI frames : .inline
I replaced the date-picker completely and implemented ".wheels" that works fine.
The datepicker is show up correctly first time. But when the user tried to add other entry or press the date entry buttton . The datepicker gets cut and is not properly visible. This is how it looks when user clicks on datepicker button second time.
What my code looks like:
import UIKit
import SwiftyJSON
import Alamofire
struct surynamaskarStruct {
var date: String
var count: String
}
class AddSuryaNamskarCountVC: UIViewController, UITextFieldDelegate {
var surynamaskarData: [surynamaskarStruct] = [
surynamaskarStruct(date: "Date", count: "0"),
surynamaskarStruct(date: "Date", count: "0"),
surynamaskarStruct(date: "Date", count: "0")
]
#IBOutlet var tableView: UITableView!
#IBOutlet weak var lblMember: UILabel!
#IBOutlet weak var btnMember: UIButton!
#IBOutlet weak var familyMemberHeightConstraint: NSLayoutConstraint!
var viewPicker : UIView!
let datePicker = UIDatePicker()
var btnIndex : Int!
var strMemberId : String!
var dicMember = [[String:Any]]()
lazy var loader : UIActivityIndicatorView = {
let indicator = UIActivityIndicatorView(style: .large)
indicator.hidesWhenStopped = true
return indicator
}()
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
if self.dicMember.count > 0 {
self.strMemberId = self.dicMember[0]["id"] as? String
self.lblMember.text = self.dicMember[0]["name"] as? String
self.familyMemberHeightConstraint.constant = 90
} else {
self.familyMemberHeightConstraint.constant = 0
}
view.addSubview(loader)
loader.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
loader.centerXAnchor.constraint(equalTo: view.centerXAnchor),
loader.centerYAnchor.constraint(equalTo: view.centerYAnchor)
])
}
override func viewWillAppear(_ animated: Bool) {
navigationBarDesign(txt_title: "Record Suryanamskar", showbtn: "back")
}
#IBAction func onMemberClick(_ sender: UIButton) {
let vc = self.storyboard?.instantiateViewController(withIdentifier: "PickerTableViewWithSearchViewController") as! PickerTableViewWithSearchViewController
vc.selectedItemCompletion = {dict in
self.lblMember.text = dict["name"] as? String
self.strMemberId = dict["id"] as? String
}
vc.dataSource = dicMember
vc.strNavigationTitle = "Family Member"
vc.modalPresentationStyle = UIModalPresentationStyle.fullScreen
self.present(vc, animated: true, completion: nil)
}
#IBAction func onCancelClick(_ sender: UIButton) {
self.dismiss(animated: true)
}
#IBAction func onSaveClick(_ sender: UIButton) {
var arrData = [[String:Any]]()
for arr in surynamaskarData {
if arr.date != "Date" && arr.count != "0" {
var dict = [String : Any]()
dict["date"] = arr.date
dict["count"] = arr.count
arrData.append(dict)
}
}
if arrData.count > 0 {
self.saveSuryaNamskarCountDataAPI(arrData)
////static controller to directly update the suryanamaskar chart instead of going back and then getting updated suryanamaskar
let alertController = UIAlertController(title:APP.title, message: "Suryanamaskar has been saved successfully!", preferredStyle:.alert)
let Action = UIAlertAction.init(title: "Ok", style: .default) { (UIAlertAction) in
// Write Your code Here
let vc = self.storyboard?.instantiateViewController(withIdentifier: "SuryaNamskarVC") as! SuryaNamskarVC
vc.modalPresentationStyle = UIModalPresentationStyle.fullScreen
// vc.modalTransitionStyle = UIModalTransitionStyle.crossDissolve
self.present(vc, animated: true, completion: nil)
}
alertController.addAction(Action)
self.present(alertController, animated: true, completion: nil)
//// Till here for alert controller. if not implemented, updated suryanamaskar will happen only once user goes back and comes back to suryanamaskarVC
}
// else {
// showAlert(title: APP.title, message: "Please select date and count before Save")
// }
}
// MARK: - DatePicker functions
func showDatePicker(){
viewPicker = UIView(frame: CGRect(x: 0.0, y: self.view.frame.height - 380, width: self.view.frame.width, height: 380))
viewPicker.backgroundColor = UIColor.white
viewPicker.clipsToBounds = true
// Posiiton date picket within a view
datePicker.frame = CGRect(x: 10, y: 50, width: self.view.frame.width, height: 200)
datePicker.datePickerMode = .date
datePicker.minimumDate = Calendar.current.date(byAdding: .month, value: -2, to: Date())
datePicker.maximumDate = Calendar.current.date(byAdding: .year, value: 0, to: Date())
datePicker.backgroundColor = UIColor.white
if #available(iOS 14.0, *) {
datePicker.preferredDatePickerStyle = .inline
} else {
if #available(iOS 13.4, *) {
datePicker.preferredDatePickerStyle = .wheels
} else {
// Fallback on earlier versions
}
// Fallback on earlier versions
}
// Add an event to call onDidChangeDate function when value is changed.
datePicker.addTarget(self, action: #selector(AddMemberStep1VC.datePickerValueChanged(_:)), for: .valueChanged)
datePicker.center.x = self.view.center.x
//ToolBar
var toolbar = UIToolbar();
toolbar.sizeToFit()
toolbar = UIToolbar(frame: CGRect(x: 0.0, y: 0.0, width: self.view.frame.width, height: 50))
let doneButton = UIBarButtonItem(title: "done".localized, style: .plain, target: self, action: #selector(donedatePicker));
let spaceButton = UIBarButtonItem(barButtonSystemItem: UIBarButtonItem.SystemItem.flexibleSpace, target: nil, action: nil)
let cancelButton = UIBarButtonItem(title: "cancel".localized, style: .plain, target: self, action: #selector(cancelDatePicker));
toolbar.setItems([doneButton,spaceButton,cancelButton], animated: false)
// txtDate.inputAccessoryView = toolbar
// txtDate.inputView = datePicker
self.viewPicker.addSubview(toolbar)
self.viewPicker.addSubview(datePicker)
self.viewPicker.clipsToBounds = true
self.view.addSubview(self.viewPicker)
}
#objc func datePickerValueChanged(_ sender: UIDatePicker){
// Create date formatter
let dateFormatter: DateFormatter = DateFormatter()
// Set date format
dateFormatter.dateFormat = "dd/MM/yyyy"
// Apply date format
let _: String = dateFormatter.string(from: sender.date)
// print("Selected value \(selectedDate)")
}
#objc func donedatePicker(){
let formatter = DateFormatter()
formatter.dateFormat = "dd/MM/yyyy"
let strDate = formatter.string(from: datePicker.date)
surynamaskarData[btnIndex].date = strDate
DispatchQueue.main.async {
self.tableView.reloadData()
}
self.viewPicker.isHidden = true
}
#objc func cancelDatePicker(){
self.viewPicker.isHidden = true
}
#objc func onAddMoreClicked() {
print("AddMore Button Pressed")
for i in surynamaskarData {
if i.date == "Date" || i.count == "0" || i.count == "" {
showAlert(title: APP.title, message: "Please select date and count before AddMore")
return
}
}
surynamaskarData.append(surynamaskarStruct(date: "Date", count: "0"))
DispatchQueue.main.async {
self.tableView.reloadData()
}
}
#objc func onDateClick(_ sender: UIButton) {
btnIndex = sender.tag
view.endEditing(true)
if self.viewPicker == nil {
self.showDatePicker()
} else {
if self.viewPicker.isHidden == true {
self.showDatePicker()
} else {
self.viewPicker.isHidden = true
}
}
}
#objc func onDeleteClick(_ sender: UIButton) {
surynamaskarData.remove(at: sender.tag)
DispatchQueue.main.async {
self.tableView.reloadData()
}
}
func textFieldDidBeginEditing(_ textField: UITextField) {
if self.viewPicker != nil {
if self.viewPicker.isHidden == false {
self.viewPicker.isHidden = false
}
}
}
func textFieldDidEndEditing(_ textField: UITextField) {
if textField.text == "" {
textField.text = "0"
}
surynamaskarData[textField.tag].count = textField.text ?? "0"
}
func json(from object:Any) -> String? {
guard let data = try? JSONSerialization.data(withJSONObject: object, options: []) else {
return nil
}
return String(data: data, encoding: String.Encoding.utf8)
}
func saveSuryaNamskarCountDataAPI(_ data : [[String:Any]]) {
var parameters: [String: Any] = [:]
parameters["surynamaskar"] = self.json(from: data)
parameters["member_id"] = self.strMemberId // _appDelegator.dicDataProfile![0]["member_id"] as? String
print(parameters)
// APIUrl.save_suryanamaskar
//live API only
//"https://myhss.org.uk/api/v1/suryanamaskar/save_suryanamaskar_count"
loader.startAnimating()
let url = URL(string: APIUrl.save_suryanamaskar)!
AF.request(url, method: .post, parameters: parameters, encoding: JSONEncoding.default)
.validate()
.responseJSON { response in
self.loader.stopAnimating()
switch response.result {
case .success(let response):
print(response)
let jsonData = JSON(response)
if let status = jsonData["status"].int
{
if status == 1
{
let strMessage : String = jsonData["message"].rawValue as! String
print(strMessage)
// create the alert
let alert = UIAlertController(title: APP.title, message: strMessage, preferredStyle: UIAlertController.Style.alert)
// add an action (button)
let ok = UIAlertAction(title: "Ok".localized, style: .default, handler: { action in
DispatchQueue.main.async {
self.dismiss(animated: true)
}
})
alert.addAction(ok)
// show the alert
self.present(alert, animated: true, completion: nil)
} else {
if let strError = jsonData["message"].string {
showAlert(title: APP.title, message: strError)
}
}
}
case .failure(let error):
print(error.localizedDescription)
showAlert(title: APP.title, message: error.localizedDescription)
}
}
}
}
extension AddSuryaNamskarCountVC : UITableViewDelegate, UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return surynamaskarData.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: AddSuryaNamskarCountTVCell.cellIdentifier) as! AddSuryaNamskarCountTVCell
cell.btnDate.tag = indexPath.row
cell.btnDate.addTarget(self, action: #selector(self.onDateClick(_:)), for: .touchUpInside)
cell.btnCancel.tag = indexPath.row
cell.btnCancel.addTarget(self, action: #selector(self.onDeleteClick(_:)), for: .touchUpInside)
cell.lblDate.text = surynamaskarData[indexPath.row].date
cell.txtCount.text = surynamaskarData[indexPath.row].count
cell.txtCount.tag = indexPath.row
cell.txtCount.delegate = self
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 60.0
}
func tableView(_ tableView: UITableView, viewForFooterInSection section: Int) -> UIView? {
let viewFooter = UIView()
viewFooter.backgroundColor = UIColor.systemGray5
let size = tableView.frame.size
let addMoreButton = UIButton()
addMoreButton.setTitle("+ Add More", for: .normal)
addMoreButton.setTitleColor(Colors.txtAppDarkColor, for: .normal)
addMoreButton.frame = CGRect(x: 0, y: 0, width: size.width, height: 50)
addMoreButton.addTarget(self, action: #selector(onAddMoreClicked), for: .touchUpInside)
viewFooter.addSubview(addMoreButton)
return viewFooter
}
func tableView(_ tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat {
return 50
}
}
Changing the Co-ordinates works:
Following co-orinates fixes the calender view for this issue.
func showDatePicker(){
viewPicker = UIView(frame: CGRect(x: 0.0, y: self.view.frame.height - 500, width: self.view.frame.width, height: 500))
viewPicker.backgroundColor = UIColor.white
viewPicker.clipsToBounds = true
// Posiiton date picket within a view
datePicker.frame = CGRect(x: 10, y: 50, width: self.view.frame.width, height: 500)
datePicker.datePickerMode = .date
datePicker.minimumDate = Calendar.current.date(byAdding: .month, value: -2, to: Date())
datePicker.maximumDate = Calendar.current.date(byAdding: .year, value: 0, to: Date())
datePicker.backgroundColor = UIColor.white
if #available(iOS 14.0, *) {
datePicker.preferredDatePickerStyle = .inline
} else {
if #available(iOS 13.4, *) {
datePicker.preferredDatePickerStyle = .wheels
} else {
// Fallback on earlier versions
}
// Fallback on earlier versions
}
// Add an event to call onDidChangeDate function when value is changed.
datePicker.addTarget(self, action: #selector(AddMemberStep1VC.datePickerValueChanged(_:)), for: .valueChanged)
datePicker.center.x = self.view.center.x
//ToolBar
var toolbar = UIToolbar();
toolbar.sizeToFit()
toolbar = UIToolbar(frame: CGRect(x: 0.0, y: 0.0, width: self.view.frame.width, height: 50))
let doneButton = UIBarButtonItem(title: "done".localized, style: .plain, target: self, action: #selector(donedatePicker));
let spaceButton = UIBarButtonItem(barButtonSystemItem: UIBarButtonItem.SystemItem.flexibleSpace, target: nil, action: nil)
let cancelButton = UIBarButtonItem(title: "cancel".localized, style: .plain, target: self, action: #selector(cancelDatePicker));
toolbar.setItems([doneButton,spaceButton,cancelButton], animated: false)
self.viewPicker.addSubview(toolbar)
self.viewPicker.addSubview(datePicker)
self.viewPicker.clipsToBounds = true
self.view.addSubview(self.viewPicker)
}
Implementing a protocol/delegate pattern as suggested by expert #DonMag, i am unable to get the code working...
I show a ratings window like below and when the user changes the ratings , i want the ratings emoji to update and the controller to pop, i also use animation, now for view i have a separate ratings class and for controller a separate class,
The problem is this function gets tapped , i can detect it but it does not reach the protocol method
#objc func ratingButtonTapped(_ sender: UIButton) {
// print(sender.titleLabel?.text)
guard let t = sender.titleLabel?.text else {
return
}
ratingDelegate?.defineTheratings(t)
}
Controller class
import UIKit
import CoreData
protocol RatingsPresentation: class {
func defineTheratings(_ ratings: String)
}
class RatingsViewController: UIViewController, RatingsPresentation {
func defineTheratings(_ ratings: String) {
print("ratings seleced\(ratings)")
self.restaurant.rating = ratings
if let appDelegate = (UIApplication.shared.delegate as? AppDelegate) {
appDelegate.saveContext()
}
if self.presentedViewController != nil {
self.dismiss(animated: true, completion: nil)
}
else {
self.navigationController?.popViewController(animated: true)
}
}
var restaurant: Restaurant!
var rates = RatingsView()
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(rates.view)
//Delegates
let vc = RatingsView()
// set the rating delegate
vc.ratingDelegate = self
//
if let restaurantImage = restaurant.image {
rates.bkgImageView.image = UIImage(data: restaurantImage as Data)
}
rates.crossBtn.addTarget(self, action: #selector(closeRatings), for: .touchUpInside)
let animateCross = CGAffineTransform(translationX: 1000, y: 0)
rates.crossBtn.transform = animateCross
}
#objc func closeRatings() {
navigationController?.popViewController(animated: true)
}
}
View Class
import UIKit
class RatingsView: UIViewController {
var bkgImageView = UIImageView()
var crossBtn = UIButton()
var btnCollection: [UIButton] = []
weak var ratingDelegate: RatingsPresentation?
override func viewDidLoad() {
super.viewDidLoad()
let ratings: [String] = ["cool", "happy","love", "sad", "angry"]
let animationBlur = UIBlurEffect(style: .dark)
let visualBlurView = UIVisualEffectView(effect: animationBlur)
// add elements to self
view.addSubview(bkgImageView)
view.addSubview(visualBlurView)
bkgImageView.translatesAutoresizingMaskIntoConstraints = false
visualBlurView.translatesAutoresizingMaskIntoConstraints = false
bkgImageView.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
bkgImageView.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
bkgImageView.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true
bkgImageView.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
// constrain visualBlurView to all 4 sides
visualBlurView.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
visualBlurView.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
visualBlurView.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true
visualBlurView.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
bkgImageView.contentMode = .scaleAspectFill
// Do any additional setup after loading the view.
//Add stackView
let stackEmoji = UIStackView()
stackEmoji.translatesAutoresizingMaskIntoConstraints = false
stackEmoji.axis = .vertical
stackEmoji.spacing = 5
stackEmoji.distribution = .fill
stackEmoji.alignment = .top
let font = UIFont(name: "Rubik-Medium", size: 28)
let fontM = UIFontMetrics(forTextStyle: .body)
ratings.forEach { (btn) in
let b = UIButton()
b.setTitle(btn, for: .normal)
b.titleLabel?.font = fontM.scaledFont(for: font!)
b.setImage(UIImage(named: btn), for: .normal)
stackEmoji.addArrangedSubview(b)
btnCollection.append(b)
//btn animation
let sizeAnimation = CGAffineTransform(scaleX: 5, y: 5)
let positionAnimation = CGAffineTransform(translationX: 1000, y: 0)
let combinedAninaton = sizeAnimation.concatenating(positionAnimation)
b.transform = combinedAninaton
b.addTarget(self, action: #selector(ratingButtonTapped(_:)), for: .touchUpInside)
}
view.addSubview(stackEmoji)
stackEmoji.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
stackEmoji.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
// if let img = UIImage(named: "cross") {
// crossBtn.setImage(img, for: [])
// }
crossBtn.setTitle("X", for: [])
crossBtn.setTitleColor(.white, for: .normal)
crossBtn.setTitleColor(.gray, for: .highlighted)
crossBtn.titleLabel?.font = UIFont.systemFont(ofSize: 44, weight: .bold)
view.addSubview(crossBtn)
crossBtn.translatesAutoresizingMaskIntoConstraints = false
crossBtn.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -30).isActive = true
crossBtn.topAnchor.constraint(equalTo: view.topAnchor, constant: 150).isActive = true
}
#objc func ratingButtonTapped(_ sender: UIButton) {
// print(sender.titleLabel?.text)
guard let t = sender.titleLabel?.text else {
return
}
ratingDelegate?.defineTheratings(t)
}
}
Problem is here
var rates = RatingsView() //// shown 1
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(rates.view) /////// you add this as a subview
//Delegates
let vc = RatingsView() //////// this is useless - not shown 1
// set the rating delegate
vc.ratingDelegate = self ////// and set the delegate for this non-shown
So you need to remove let vc = RatingsView() and set the delegate for
rates.ratingDelegate = self
I am using a WKWebView in my Swift app to present some textfields.
I set some appearance properties to match a specific design, in this case its background has to be blue.
But when the keyboard is triggered by the WKWebView, it does something with the appearance properties and shows the keyboard toolbar in a pale light appearance of my color, do you know why?
The only appearance manipulation on UIToolBar that somewhat worked is this one:
UIToolbar.appearance().backgroundColor = .blue
This is my problem:
This is my goal:
Found a way, ended up to swizzle UIToolbars. Hopefully everything is there, but you would get an idea. Swift 4:
class YourController: UIViewController {
#IBOutlet weak var webView: PWebView!
var toolbar : UIToolbar?
func viewDidLoad() {
webView.addInputAccessoryView(toolbar: self.getToolbar(height: 44))
}
func getToolbar(height: Int) -> UIToolbar? {
let toolBar = UIToolbar()
toolBar.frame = CGRect(x: 0, y: 50, width: 320, height: height)
toolBar.barStyle = .black
toolBar.tintColor = .white
toolBar.barTintColor = UIColor.blue
let doneButton = UIBarButtonItem(title: "Done", style: .plain, target: self, action: #selector(onToolbarDoneClick(sender:)) )
let flexibleSpaceItem = UIBarButtonItem(barButtonSystemItem: .flexibleSpace, target: self, action: nil )
toolBar.setItems([flexibleSpaceItem, doneButton], animated: false)
toolBar.isUserInteractionEnabled = true
toolBar.sizeToFit()
return toolBar
}
#objc func onToolbarDoneClick(sender: UIBarButtonItem) {
webView?.resignFirstResponder()
}
}
var ToolbarHandle: UInt8 = 0
extension WKWebView {
func addInputAccessoryView(toolbar: UIView?) {
guard let toolbar = toolbar else {return}
objc_setAssociatedObject(self, &ToolbarHandle, toolbar, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC)
var candidateView: UIView? = nil
for view in self.scrollView.subviews {
let description : String = String(describing: type(of: view))
if description.hasPrefix("WKContent") {
candidateView = view
break
}
}
guard let targetView = candidateView else {return}
let newClass: AnyClass? = classWithCustomAccessoryView(targetView: targetView)
guard let targetNewClass = newClass else {return}
object_setClass(targetView, targetNewClass)
}
func classWithCustomAccessoryView(targetView: UIView) -> AnyClass? {
guard let _ = targetView.superclass else {return nil}
let customInputAccesoryViewClassName = "_CustomInputAccessoryView"
var newClass: AnyClass? = NSClassFromString(customInputAccesoryViewClassName)
if newClass == nil {
newClass = objc_allocateClassPair(object_getClass(targetView), customInputAccesoryViewClassName, 0)
} else {
return newClass
}
let newMethod = class_getInstanceMethod(WKWebView.self, #selector(WKWebView.getCustomInputAccessoryView))
class_addMethod(newClass.self, #selector(getter: WKWebView.inputAccessoryView), method_getImplementation(newMethod!), method_getTypeEncoding(newMethod!))
objc_registerClassPair(newClass!)
return newClass
}
#objc func getCustomInputAccessoryView() -> UIView? {
var superWebView: UIView? = self
while (superWebView != nil) && !(superWebView is WKWebView) {
superWebView = superWebView?.superview
}
guard let webView = superWebView else {return nil}
let customInputAccessory = objc_getAssociatedObject(webView, &ToolbarHandle)
return customInputAccessory as? UIView
}
}
private var keyBordView: UIView?
override func viewDidLoad() {
super.viewDidLoad()
let webView = WKWebView.init(frame: view.bounds)
webView.loadHTMLString("<html><body><div contenteditable='true'></div></body></html>", baseURL: nil)
view.addSubview(webView)
for subview in webView.scrollView.subviews {
if subview.classForCoder.description() == "WKContentView" {
keyBordView = subview
}
}
NotificationCenter.default.addObserver(self, selector: #selector(keyboardShow), name: .UIKeyboardDidShow, object: nil)
// Do any additional setup after loading the view.
}
#objc private func keyboardShow() {
let keyboardToolbar = UIToolbar()
keyboardToolbar.backgroundColor = UIColor.blue
keyboardToolbar.sizeToFit()
let flexBarButton = UIBarButtonItem(barButtonSystemItem: .flexibleSpace, target: nil, action: nil)
let doneBarButton = UIBarButtonItem(barButtonSystemItem: .done, target: self, action: #selector(self.dismissKeyBord))
keyboardToolbar.items = [flexBarButton, doneBarButton]
keyBordView = keyboardToolbar
}
So I have this snippet of code that is supposed to handle making my content scroll to the bottom upon toggling of the keyboard. However, all it does is crash every time that I try to do it.
Any insight?
Here is my code:
#objc func handleKeyboardNotification(notification: NSNotification){
if let userinfo = notification.userInfo{
let keyboardFrame = (userinfo[UIKeyboardFrameEndUserInfoKey] as! NSValue).cgRectValue
self.bottomConstraint?.constant = -(keyboardFrame.height)
let isKeyboardShowing = notification.name == NSNotification.Name.UIKeyboardWillShow
self.bottomConstraint?.constant = isKeyboardShowing ? -(keyboardFrame.height) : 0
UIView.animate(withDuration: 0, delay: 0, options: UIViewAnimationOptions.curveEaseOut, animations: {
self.view.layoutIfNeeded()
}, completion: { (completion) in
if self.comments.count > 0 && isKeyboardShowing {
let indexPath = IndexPath(item: self.comments.count-1, section: 0)
self.collectionView.scrollToItem(at: indexPath, at: .top, animated: true)
}
})
}
}
I am currently using IGLitkit to populate my view controller. The items are controlled and loaded through this view controller.
import UIKit
import IGListKit
import Foundation
protocol CommentsSectionDelegate: class {
func CommentSectionUpdared(sectionController: CommentsSectionController)
}
class CommentsSectionController: ListSectionController,CommentCellDelegate {
weak var delegate: CommentsSectionDelegate? = nil
var comment: CommentGrabbed?
var eventKey: String?
override init() {
super.init()
// supplementaryViewSource = self
//sets the spacing between items in a specfic section controller
inset = UIEdgeInsets(top: 5, left: 0, bottom: 0, right: 0)
}
// MARK: IGListSectionController Overrides
override func numberOfItems() -> Int {
return 1
}
override func sizeForItem(at index: Int) -> CGSize {
let frame = CGRect(x: 0, y: 0, width: collectionContext!.containerSize.width, height: 50)
let dummyCell = CommentCell(frame: frame)
dummyCell.comment = comment
dummyCell.layoutIfNeeded()
let targetSize = CGSize(width: collectionContext!.containerSize.width, height: 55)
let estimatedSize = dummyCell.systemLayoutSizeFitting(targetSize)
let height = max(40+8+8, estimatedSize.height)
return CGSize(width: collectionContext!.containerSize.width, height: height)
}
override var minimumLineSpacing: CGFloat {
get {
return 0.0
}
set {
self.minimumLineSpacing = 0.0
}
}
override func cellForItem(at index: Int) -> UICollectionViewCell {
guard let cell = collectionContext?.dequeueReusableCell(of: CommentCell.self, for: self, at: index) as? CommentCell else {
fatalError()
}
// print(comment)
cell.comment = comment
cell.delegate = self
return cell
}
override func didUpdate(to object: Any) {
comment = object as? CommentGrabbed
}
override func didSelectItem(at index: Int){
}
/*
func supportedElementKinds() -> [String] {
return [UICollectionElementKindSectionHeader]
}
func viewForSupplementaryElement(ofKind elementKind: String, at index: Int) -> UICollectionReusableView {
guard let view = collectionContext?.dequeueReusableSupplementaryView(ofKind: elementKind, for: self, class: CommentHeader.self, at: index) as? CommentHeader else{
fatalError()
}
view.handle = "Comments"
return view
}
*/
// func optionsButtonTapped(cell: CommentCell) {
// print("like")
//
//
// }
func optionsButtonTapped(cell: CommentCell){
print("like")
let comment = self.comment
_ = comment?.uid
// 3
let alertController = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet)
// 4
if comment?.uid != User.current.uid {
let flagAction = UIAlertAction(title: "Report as Inappropriate", style: .default) { _ in
ChatService.flag(comment!)
let okAlert = UIAlertController(title: nil, message: "The post has been flagged.", preferredStyle: .alert)
okAlert.addAction(UIAlertAction(title: "Ok", style: .default))
self.viewController?.present(okAlert, animated: true, completion: nil)
}
let cancelAction = UIAlertAction(title: "Cancel", style: .cancel, handler: nil)
alertController.addAction(cancelAction)
alertController.addAction(flagAction)
}else{
let cancelAction = UIAlertAction(title: "Cancel", style: .cancel, handler: nil)
let deleteAction = UIAlertAction(title: "Delete Comment", style: .default, handler: { _ in
ChatService.deleteComment(comment!, (comment?.eventKey)!)
let okAlert = UIAlertController(title: nil, message: "Comment Has Been Deleted", preferredStyle: .alert)
okAlert.addAction(UIAlertAction(title: "Ok", style: .default))
self.viewController?.present(okAlert, animated: true, completion: nil)
self.onItemDeleted()
})
alertController.addAction(cancelAction)
alertController.addAction(deleteAction)
}
self.viewController?.present(alertController, animated: true, completion: nil)
}
func onItemDeleted() {
delegate?.CommentSectionUpdared(sectionController: self)
}
/*
func sizeForSupplementaryView(ofKind elementKind: String, at index: Int) -> CGSize {
return CGSize(width: collectionContext!.containerSize.width, height: 40)
}
*/
}
Also note the data source
extension NewCommentsViewController: ListAdapterDataSource {
// 1 objects(for:) returns an array of data objects that should show up in the collection view. loader.entries is provided here as it contains the journal entries.
func objects(for listAdapter: ListAdapter) -> [ListDiffable] {
var items:[ListDiffable] = comments
print("comments = \(comments)")
return [addHeader] + items
}
// 2 For each data object, listAdapter(_:sectionControllerFor:) must return a new instance of a section controller. For now you’re returning a plain IGListSectionController to appease the compiler — in a moment, you’ll modify this to return a custom journal section controller.
func listAdapter(_ listAdapter: ListAdapter, sectionControllerFor object: Any) -> ListSectionController {
//the comment section controller will be placed here but we don't have it yet so this will be a placeholder
if let object = object as? ListDiffable, object === addHeader {
return CommentsHeaderSectionController()
}
let sectionController = CommentsSectionController()
sectionController.delegate = self
return sectionController
}
// 3 emptyView(for:) returns a view that should be displayed when the list is empty. NASA is in a bit of a time crunch, so they didn’t budget for this feature.
func emptyView(for listAdapter: ListAdapter) -> UIView? {
let view = UIView()
view.backgroundColor = UIColor.white
return view
}
}
Controller which contains keyboard function that causes crash
import UIKit
import IGListKit
import Firebase
class NewCommentsViewController: UIViewController, UITextFieldDelegate,CommentsSectionDelegate {
//array of comments which will be loaded by a service function
var comments = [CommentGrabbed]()
var messagesRef: DatabaseReference?
var bottomConstraint: NSLayoutConstraint?
public let addHeader = "addHeader" as ListDiffable
public var eventKey = ""
//This creates a lazily-initialized variable for the IGListAdapter. The initializer requires three parameters:
//1 updater is an object conforming to IGListUpdatingDelegate, which handles row and section updates. IGListAdapterUpdater is a default implementation that is suitable for your usage.
//2 viewController is a UIViewController that houses the adapter. This view controller is later used for navigating to other view controllers.
//3 workingRangeSize is the size of the working range, which allows you to prepare content for sections just outside of the visible frame.
lazy var adapter: ListAdapter = {
return ListAdapter(updater: ListAdapterUpdater(), viewController: self)
}()
// 1 IGListKit uses IGListCollectionView, which is a subclass of UICollectionView, which patches some functionality and prevents others.
let collectionView: UICollectionView = {
// 2 This starts with a zero-sized rect since the view isn’t created yet. It uses the UICollectionViewFlowLayout just as the ClassicFeedViewController did.
let view = UICollectionView(frame: CGRect.zero, collectionViewLayout: UICollectionViewFlowLayout())
// 3 The background color is set to white
view.backgroundColor = UIColor.white
return view
}()
//will fetch the comments from the database and append them to an array
fileprivate func fetchComments(){
comments.removeAll()
messagesRef = Database.database().reference().child("Comments").child(eventKey)
print(eventKey)
// print(comments.count)
var query = messagesRef?.queryOrderedByKey()
query?.observe(.value, with: { (snapshot) in
guard var allObjects = snapshot.children.allObjects as? [DataSnapshot] else {
return
}
print(snapshot)
allObjects.forEach({ (snapshot) in
guard let commentDictionary = snapshot.value as? [String: Any] else{
return
}
guard let uid = commentDictionary["uid"] as? String else{
return
}
UserService.show(forUID: uid, completion: { (user) in
if let user = user {
var commentFetched = CommentGrabbed(user: user, dictionary: commentDictionary)
commentFetched.commentID = snapshot.key
let filteredArr = self.comments.filter { (comment) -> Bool in
return comment.commentID == commentFetched.commentID
}
if filteredArr.count == 0 {
self.comments.append(commentFetched)
}
self.adapter.performUpdates(animated: true)
}
self.comments.sort(by: { (comment1, comment2) -> Bool in
return comment1.creationDate.compare(comment2.creationDate) == .orderedAscending
})
self.comments.forEach({ (comments) in
})
})
})
}, withCancel: { (error) in
print("Failed to observe comments")
})
//first lets fetch comments for current event
}
lazy var submitButton : UIButton = {
let submitButton = UIButton(type: .system)
submitButton.setTitle("Submit", for: .normal)
submitButton.setTitleColor(.black, for: .normal)
submitButton.titleLabel?.font = UIFont.boldSystemFont(ofSize: 14)
submitButton.addTarget(self, action: #selector(handleSubmit), for: .touchUpInside)
submitButton.isEnabled = false
return submitButton
}()
//allows you to gain access to the input accessory view that each view controller has for inputting text
lazy var containerView: UIView = {
let containerView = UIView()
containerView.backgroundColor = .white
containerView.addSubview(self.submitButton)
self.submitButton.anchor(top: containerView.topAnchor, left: nil, bottom: containerView.bottomAnchor, right: containerView.rightAnchor, paddingTop: 0, paddingLeft: 0, paddingBottom: 0, paddingRight: 12, width: 50, height: 0)
containerView.addSubview(self.commentTextField)
self.commentTextField.anchor(top: containerView.topAnchor, left: containerView.leftAnchor, bottom: containerView.bottomAnchor, right: self.submitButton.leftAnchor, paddingTop: 0, paddingLeft: 12, paddingBottom: 0, paddingRight: 0, width: 0, height: 0)
self.commentTextField.delegate = self
let lineSeparatorView = UIView()
lineSeparatorView.backgroundColor = UIColor.rgb(red: 230, green: 230, blue: 230)
containerView.addSubview(lineSeparatorView)
lineSeparatorView.anchor(top: containerView.topAnchor, left: containerView.leftAnchor, bottom: nil, right: containerView.rightAnchor, paddingTop: 0, paddingLeft: 0, paddingBottom: 0, paddingRight: 0, width: 0, height: 0.5)
return containerView
}()
lazy var commentTextField: UITextField = {
let textField = UITextField()
textField.placeholder = "Add a comment"
textField.delegate = self
textField.addTarget(self, action: #selector(textFieldDidChange(_:)), for: .editingChanged)
return textField
}()
#objc func textFieldDidChange(_ textField: UITextField) {
let isCommentValid = commentTextField.text?.characters.count ?? 0 > 0
if isCommentValid {
submitButton.isEnabled = true
}else{
submitButton.isEnabled = false
}
}
#objc func handleSubmit(){
guard let comment = commentTextField.text, comment.characters.count > 0 else{
return
}
let userText = Comments(content: comment, uid: User.current.uid, profilePic: User.current.profilePic!,eventKey: eventKey)
sendMessage(userText)
// will remove text after entered
self.commentTextField.text = nil
}
#objc func handleKeyboardNotification(notification: NSNotification){
if let userinfo = notification.userInfo {
if let keyboardFrame = (userinfo[UIKeyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue {
self.bottomConstraint?.constant = -(keyboardFrame.height)
let isKeyboardShowing = notification.name == NSNotification.Name.UIKeyboardWillShow
self.bottomConstraint?.constant = isKeyboardShowing ? -(keyboardFrame.height) : 0
UIView.animate(withDuration: 0, delay: 0, options: UIViewAnimationOptions.curveEaseOut, animations: {
self.view.layoutIfNeeded()
}, completion: { (completion) in
if self.comments.count > 0 && isKeyboardShowing {
let indexPath = IndexPath(item: self.comments.count-1, section: 0)
self.collectionView.scrollToItem(at: indexPath, at: .top, animated: true)
}
})
}
}
}
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(collectionView)
collectionView.addSubview(containerView)
collectionView.alwaysBounceVertical = true
containerView.anchor(top: nil, left: view.leftAnchor, bottom: nil, right: view.rightAnchor, paddingTop: 0, paddingLeft: 0, paddingBottom: 0, paddingRight: 0, width: 0, height: 40)
//view.addConstraintsWithFormatt("H:|[v0]|", views: containerView)
// view.addConstraintsWithFormatt("V:[v0(48)]", views: containerView)
bottomConstraint = NSLayoutConstraint(item: containerView, attribute: .bottom, relatedBy: .equal, toItem: view, attribute: .bottom, multiplier: 1, constant: 0)
view.addConstraint(bottomConstraint!)
adapter.collectionView = collectionView
adapter.dataSource = self
NotificationCenter.default.addObserver(self, selector: #selector(handleKeyboardNotification), name: NSNotification.Name.UIKeyboardWillShow, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(handleKeyboardNotification), name: NSNotification.Name.UIKeyboardWillHide, object: nil)
collectionView.register(CommentCell.self, forCellWithReuseIdentifier: "CommentCell")
collectionView.register(CommentHeader.self, forCellWithReuseIdentifier: "HeaderCell")
self.collectionView.contentInset = UIEdgeInsetsMake(20, 0, 0, 0)
fetchComments()
// Do any additional setup after loading the view.
}
//look here
func CommentSectionUpdared(sectionController: CommentsSectionController){
print("like")
self.fetchComments()
self.adapter.performUpdates(animated: true)
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
tabBarController?.tabBar.isHidden = true
submitButton.isUserInteractionEnabled = true
}
//viewDidLayoutSubviews() is overridden, setting the collectionView frame to match the view bounds.
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
collectionView.frame = view.bounds
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
extension NewCommentsViewController: ListAdapterDataSource {
// 1 objects(for:) returns an array of data objects that should show up in the collection view. loader.entries is provided here as it contains the journal entries.
func objects(for listAdapter: ListAdapter) -> [ListDiffable] {
var items:[ListDiffable] = comments
print("comments = \(comments)")
return [addHeader] + items
}
// 2 For each data object, listAdapter(_:sectionControllerFor:) must return a new instance of a section controller. For now you’re returning a plain IGListSectionController to appease the compiler — in a moment, you’ll modify this to return a custom journal section controller.
func listAdapter(_ listAdapter: ListAdapter, sectionControllerFor object: Any) -> ListSectionController {
//the comment section controller will be placed here but we don't have it yet so this will be a placeholder
if let object = object as? ListDiffable, object === addHeader {
return CommentsHeaderSectionController()
}
let sectionController = CommentsSectionController()
sectionController.delegate = self
return sectionController
}
// 3 emptyView(for:) returns a view that should be displayed when the list is empty. NASA is in a bit of a time crunch, so they didn’t budget for this feature.
func emptyView(for listAdapter: ListAdapter) -> UIView? {
let view = UIView()
view.backgroundColor = UIColor.white
return view
}
}
extension NewCommentsViewController {
func sendMessage(_ message: Comments) {
ChatService.sendMessage(message, eventKey: eventKey)
}
}
This Code Seems to be crashing my code.
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'attempt to scroll to invalid
index path: {length = 2, path = 0 - 3}'
func handleKeyboardNotification(notification: NSNotification) {
if let userinfo = notification.userInfo {
let keyboardFrame = (userinfo[UIKeyboardFrameEndUserInfoKey] as! NSValue).cgRectValue
self.bottomConstraint?.constant = -(keyboardFrame.height)
let isKeyboardShowing = notification.name == NSNotification.Name.UIKeyboardWillShow
self.bottomConstraint?.constant = isKeyboardShowing ? -keyboardFrame.height : 0
UIView.animate(withDuration: 0, delay: 0, options: UIViewAnimationOptions.curveEaseOut, animations: {
self.view.layoutIfNeeded()
}, completion: { (completion) in
if self.comments.count > 0 && isKeyboardShowing {
let indexPath = NSIndexPath(item: self.comments.count - 1, section: 0)
self.collectionView.scrollToItem(at: indexPath as IndexPath, at: .top, animated: true)
}
})
}
}
I am also using IGListKit to populate the collection view that this is contained in. Below is my comments controller.
import UIKit
import IGListKit
import Firebase
class NewCommentsViewController: UIViewController, UITextFieldDelegate {
//array of comments which will be loaded by a service function
var comments = [CommentGrabbed]()
var messagesRef: DatabaseReference?
var bottomConstraint: NSLayoutConstraint?
public var eventKey = ""
//This creates a lazily-initialized variable for the IGListAdapter. The initializer requires three parameters:
//1 updater is an object conforming to IGListUpdatingDelegate, which handles row and section updates. IGListAdapterUpdater is a default implementation that is suitable for your usage.
//2 viewController is a UIViewController that houses the adapter. This view controller is later used for navigating to other view controllers.
//3 workingRangeSize is the size of the working range, which allows you to prepare content for sections just outside of the visible frame.
lazy var adapter: ListAdapter = {
return ListAdapter(updater: ListAdapterUpdater(), viewController: self, workingRangeSize: 0)
}()
// 1 IGListKit uses IGListCollectionView, which is a subclass of UICollectionView, which patches some functionality and prevents others.
let collectionView: UICollectionView = {
// 2 This starts with a zero-sized rect since the view isn’t created yet. It uses the UICollectionViewFlowLayout just as the ClassicFeedViewController did.
let view = UICollectionView(frame: CGRect.zero, collectionViewLayout: UICollectionViewFlowLayout())
// 3 The background color is set to white
view.backgroundColor = UIColor.white
return view
}()
//will fetch the comments from the database and append them to an array
fileprivate func fetchComments() {
messagesRef = Database.database().reference().child("Comments").child(eventKey)
print(eventKey)
print(comments.count)
messagesRef?.observe(.childAdded, with: { (snapshot) in
print(snapshot)
guard let commentDictionary = snapshot.value as? [String: Any] else {
return
}
print(commentDictionary)
guard let uid = commentDictionary["uid"] as? String else {
return
}
UserService.show(forUID: uid, completion: { (user) in
if let user = user {
var commentFetched = CommentGrabbed(user: user, dictionary: commentDictionary)
commentFetched.commentID = snapshot.key
let filteredArr = self.comments.filter { (comment) -> Bool in
return comment.commentID == commentFetched.commentID
}
if filteredArr.count == 0 {
self.comments.append(commentFetched)
}
print(self.comments)
self.adapter.performUpdates(animated: true)
}
self.comments.sort(by: { (comment1, comment2) -> Bool in
return comment1.creationDate.compare(comment2.creationDate) == .orderedAscending
})
})
}, withCancel: { (error) in
print("Failed to observe comments")
})
//first lets fetch comments for current event
}
lazy var submitButton: UIButton = {
let submitButton = UIButton(type: .system)
submitButton.setTitle("Submit", for: .normal)
submitButton.setTitleColor(.black, for: .normal)
submitButton.titleLabel?.font = UIFont.boldSystemFont(ofSize: 14)
submitButton.addTarget(self, action: #selector(handleSubmit), for: .touchUpInside)
submitButton.isEnabled = false
return submitButton
}()
//allows you to gain access to the input accessory view that each view controller has for inputting text
lazy var containerView: UIView = {
let containerView = UIView()
containerView.backgroundColor = .white
containerView.addSubview(self.submitButton)
self.submitButton.anchor(top: containerView.topAnchor, left: nil, bottom: containerView.bottomAnchor, right: containerView.rightAnchor, paddingTop: 0, paddingLeft: 0, paddingBottom: 0, paddingRight: 12, width: 50, height: 0)
containerView.addSubview(self.commentTextField)
self.commentTextField.anchor(top: containerView.topAnchor, left: containerView.leftAnchor, bottom: containerView.bottomAnchor, right: self.submitButton.leftAnchor, paddingTop: 0, paddingLeft: 12, paddingBottom: 0, paddingRight: 180, width: 0, height: 0)
self.commentTextField.delegate = self
let lineSeparatorView = UIView()
lineSeparatorView.backgroundColor = UIColor.rgb(red: 230, green: 230, blue: 230)
containerView.addSubview(lineSeparatorView)
lineSeparatorView.anchor(top: containerView.topAnchor, left: containerView.leftAnchor, bottom: nil, right: containerView.rightAnchor, paddingTop: 0, paddingLeft: 0, paddingBottom: 0, paddingRight: 0, width: 0, height: 0.5)
return containerView
}()
lazy var commentTextField: UITextField = {
let textField = UITextField()
textField.placeholder = "Add a comment"
textField.delegate = self
textField.addTarget(self, action: #selector(textFieldDidChange(_:)), for: .editingChanged)
return textField
}()
func textFieldDidChange(_ textField: UITextField) {
let isCommentValid = commentTextField.text?.characters.count ?? 0 > 0
if isCommentValid {
submitButton.isEnabled = true
} else {
submitButton.isEnabled = false
}
}
func handleSubmit() {
guard let comment = commentTextField.text, comment.characters.count > 0 else {
return
}
let userText = Comments(content: comment, uid: User.current.uid, profilePic: User.current.profilePic!)
sendMessage(userText)
// will remove text after entered
self.commentTextField.text = nil
}
func flagButtonTapped (from cell: CommentCell) {
guard let indexPath = collectionView.indexPath(for: cell) else { return }
// 2
let comment = comments[indexPath.item]
// 3
let alertController = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet)
// 4
if comment.uid != User.current.uid {
let flagAction = UIAlertAction(title: "Report as Inappropriate", style: .default) { _ in
ChatService.flag(comment)
let okAlert = UIAlertController(title: nil, message: "The post has been flagged.", preferredStyle: .alert)
okAlert.addAction(UIAlertAction(title: "Ok", style: .default))
self.present(okAlert, animated: true)
}
let cancelAction = UIAlertAction(title: "Cancel", style: .cancel, handler: nil)
alertController.addAction(cancelAction)
alertController.addAction(flagAction)
} else {
let cancelAction = UIAlertAction(title: "Cancel", style: .cancel, handler: nil)
let deleteAction = UIAlertAction(title: "Delete Comment", style: .default, handler: { _ in
ChatService.deleteComment(comment, self.eventKey)
let okAlert = UIAlertController(title: nil, message: "Comment Has Been Deleted", preferredStyle: .alert)
okAlert.addAction(UIAlertAction(title: "Ok", style: .default))
self.present(okAlert, animated: true)
})
alertController.addAction(cancelAction)
alertController.addAction(deleteAction)
}
present(alertController, animated: true, completion: nil)
}
func handleKeyboardNotification(notification: NSNotification) {
if let userinfo = notification.userInfo {
let keyboardFrame = (userinfo[UIKeyboardFrameEndUserInfoKey] as! NSValue).cgRectValue
self.bottomConstraint?.constant = -keyboardFrame.height
let isKeyboardShowing = notification.name == NSNotification.Name.UIKeyboardWillShow
self.bottomConstraint?.constant = isKeyboardShowing ? -keyboardFrame.height : 0
UIView.animate(withDuration: 0, delay: 0, options: .curveEaseOut, animations: {
self.view.layoutIfNeeded()
}, completion: { (completion) in
if self.comments.count > 0 && isKeyboardShowing {
let indexPath = NSIndexPath(item: self.comments.count - 1, section: 0)
self.collectionView.scrollToItem(at: indexPath as IndexPath, at: .top, animated: true)
}
})
}
}
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(collectionView)
collectionView.addSubview(containerView)
collectionView.alwaysBounceVertical = true
view.addConstraintsWithFormatt("H:|[v0]|", views: containerView)
view.addConstraintsWithFormatt("V:[v0(48)]", views: containerView)
bottomConstraint = NSLayoutConstraint(item: containerView, attribute: .bottom, relatedBy: .equal, toItem: view, attribute: .bottom, multiplier: 1, constant: 0)
view.addConstraint(bottomConstraint!)
adapter.collectionView = collectionView
adapter.dataSource = self
NotificationCenter.default.addObserver(self, selector: #selector(handleKeyboardNotification), name: NSNotification.Name.UIKeyboardWillShow, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(handleKeyboardNotification), name: NSNotification.Name.UIKeyboardWillHide, object: nil)
collectionView.register(CommentCell.self, forCellWithReuseIdentifier: "CommentCell")
fetchComments()
// Do any additional setup after loading the view.
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
tabBarController?.tabBar.isHidden = true
submitButton.isUserInteractionEnabled = true
}
//viewDidLayoutSubviews() is overridden, setting the collectionView frame to match the view bounds.
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
collectionView.frame = view.bounds
}
}
extension NewCommentsViewController: ListAdapterDataSource {
// 1 objects(for:) returns an array of data objects that should show up in the collection view. loader.entries is provided here as it contains the journal entries.
func objects(for listAdapter: ListAdapter) -> [ListDiffable] {
print("comments = \(comments)")
return comments
}
// 2 For each data object, listAdapter(_:sectionControllerFor:) must return a new instance of a section controller. For now you’re returning a plain IGListSectionController to appease the compiler — in a moment, you’ll modify this to return a custom journal section controller.
func listAdapter(_ listAdapter: ListAdapter, sectionControllerFor object: Any) -> ListSectionController {
//the comment section controller will be placed here but we don't have it yet so this will be a placeholder
return CommentsSectionController()
}
// 3 emptyView(for:) returns a view that should be displayed when the list is empty. NASA is in a bit of a time crunch, so they didn’t budget for this feature.
func emptyView(for listAdapter: ListAdapter) -> UIView? {
let view = UIView()
view.backgroundColor = UIColor.white
return view
}
}
extension NewCommentsViewController {
func sendMessage(_ message: Comments) {
ChatService.sendMessage(message, eventKey: eventKey)
}
}
Also this is my section controller. Now it is loading the data but as soon as I try to add someething to test if it is working properly using the keyboard it crashes.
import UIKit
import IGListKit
import Foundation
class CommentsSectionController: ListSectionController {
var comment: CommentGrabbed?
override init() {
super.init()
inset = UIEdgeInsets(top: 0, left: 0, bottom: 15, right: 0)
}
// MARK: IGListSectionController Overrides
override func numberOfItems() -> Int {
print(numberOfItems)
return 1
}
override func sizeForItem(at index: Int) -> CGSize {
let frame = CGRect(x: 0, y: 0, width: collectionContext!.containerSize.width, height: 50)
let dummyCell = CommentCell(frame: frame)
dummyCell.comment = comment
dummyCell.layoutIfNeeded()
let targetSize = CGSize(width: collectionContext!.containerSize.width, height: 55)
let estimatedSize = dummyCell.systemLayoutSizeFitting(targetSize)
let height = max(40 + 8 + 8, estimatedSize.height)
return CGSize(width: collectionContext!.containerSize.width, height: height)
}
override var minimumLineSpacing: CGFloat {
get {
return 0.0
}
set {
self.minimumLineSpacing = 0.0
}
}
override func cellForItem(at index: Int) -> UICollectionViewCell {
guard let cell = collectionContext?.dequeueReusableCell(of: CommentCell.self, for: self, at: index) as? CommentCell else {
fatalError()
}
print(comment)
cell.comment = comment
return cell
}
override func didUpdate(to object: Any) {
comment = object as! CommentGrabbed
}
override func didSelectItem(at index: Int) {
}
}
This specific method is responsbile for returning cells
func listAdapter(_ listAdapter: ListAdapter, sectionControllerFor object: Any) -> ListSectionController {
//the comment section controller will be placed here but we don't have it yet so this will be a placeholder
return CommentsSectionController()
}
Any insight would be very helpful
This is happening because you set the number of items for your collection to be equal to 1, but you are trying to access a cell that has an index equal to IndexPath(section: 0, item: self:comments.count-1), and comments.count-1 is equal to 3 in this case as you can guess from the error log, which is why you get this error.
The solution here would be returning the correct amount of comments (comments.count-1) for your collection view data source.
UPDATE:
In that case, you should not use NSIndexPath. Instead, use IndexPath. Also, after this change you no longer need to cast your index path as IndexPath in your scrollToItem call. The updated version would look like this:
func handleKeyboardNotification(notification: NSNotification) {
// ...
UIView.animate(withDuration: 0, delay: 0, options: UIViewAnimationOptions.curveEaseOut, animations: {
self.view.layoutIfNeeded()
}, completion: { (completion) in
if self.comments.count > 0 && isKeyboardShowing {
let indexPath = IndexPath(item: self.comments.count-1, section: 0)
self.collectionView.scrollToItem(at: indexPath, at: .top, animated: true)
}
})
}
}