I am getting my feet wet with Swift and I am wondering what proper style for guards is. Am I using the guard statement correctly? It feels a little clunky, but I prefer it to an if let statement. Or could this be simplified?
import UIKit
class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
var tableView: UITableView?
...
required init() {
super.init(nibName:nil, bundle:nil)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func viewDidLoad() {
super.viewDidLoad()
setupUI()
}
// MARK: - User Interface
func setupUI() {
...
// tableView
tableView = UITableView()
guard let tableView = tableView else {
Log.msg("tableView could not be initialized")
return
}
tableView.delegate = self
tableView.dataSource = self
tableView.separatorInset = UIEdgeInsetsZero
tableView.layoutMargins = UIEdgeInsetsZero
tableView.accessibilityIdentifier = "tableView"
tableView.translatesAutoresizingMaskIntoConstraints = false
self.view.addSubview(tableView)
...
}
}
Log is a custom logging class that has optimized console output.
Although I think this question should be moved to the codereview site, I'll mention this:
You are using the guard statement correctly from a syntactic point of view. It's a way to check something and return early if the condition isn't met. Your overall implementation needs a bit of refactoring though so you don't need the guard at all.
When creating a view controller, do this instead:
class ViewController: UIViewController {
private var tableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
tableView = {
let tableView = UITableView()
tableView.property = ...
return tableView
}()
view.addSubview(tableView)
// setup constraints on tableView...
}
}
By using an implicitly unwrapped optional for your tableView property you forego the need for a guard and testing the tableView for nil wherever you need to use it, but you also get the benefit of deferred initialization (in viewDidLoad()).
Related
I'm having an issue calling a segue from a TableView header that is associated with a programatically created TableView. As a stop-gap I have saved an object reference to the main UITableViewController using a Singleton but this is not the ideal solution.
My TableViewController has multiple sections with either 1 or 2 rows within each section depending on whether the top-level section row is selected. The second row is effectively used to expand on the content that is displayed on the selected row.
The second row contains a custom cell that contains a slider menu, and depending on the menu item selected, 1 of 5 subviews are displayed in the container view.
One of these subviews contains a programatically generated TableView with its own custom header and a custom cell. This cell contains a header and an embedded button. When the button is pressed in the header, I want to segue to another navigation controller, however, my problem is I cannot correctly initialise a delegate to access the main TableViewController.
It gets a bit more complex, as the custom cell creates its own TableView and handles its own functions for operations that are normally performed on the main controller using overrides e.g. didSelectRowAt, numberOfRowsInSelection, headerForRowAt, etc.
class pendingNotificationCustomCell: UITableViewCell, UITableViewDataSource, UITableViewDelegate {
var dataSample:[String] = ["Row 1,"Row 2", "Row 3"]
var PendingTableView: UITableView?
required init(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func awakeFromNib() {
print("##### awakeFromNib")
super.awakeFromNib()
// Initialization code
setUpTable()
}
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style , reuseIdentifier: reuseIdentifier)
setUpTable()
}
func setUpTable() {
PendingTableView = UITableView(frame: CGRect.init(), style:UITableView.Style.plain)
PendingTableView?.delegate = self
PendingTableView?.dataSource = self
PendingTableView?.register(MyCell.self, forCellReuseIdentifier: "pendingnotificationsCellID")
PendingTableView?.register(PendingNotificationsHeader.self, forHeaderFooterViewReuseIdentifier: "pendingHeaderID")
PendingTableView?.sectionHeaderHeight = 40
PendingTableView?.allowsSelection = true
PendingTableView?.allowsMultipleSelection = false
self.addSubview(PendingTableView!)
}
I can easily setup protocols and associated delegates to access functions on the controller that manages the main TableView custom cells. However, as the problematic custom cell does not inherit functions relating to performing segues I need to access the function on the main controller.
I've experimented quite a bit with this and haven't been able to come up with a viable solution other than the Singleton hack.
let pendingCell = pendingNotificationCustomCell()
pendingCell.delegate4 = mStats.mainController
When I try assigning delegate4 with an initialised outlet that references the main TableViewController it always has a value of 'nil' when it gets there. Even when I assign it with the Singleton value in the class the generates the second
TableView. The commented out line fails whereas calling the method using the mStats Singleton works fine.
//delegate4.callSegueFromCell2(myData: myData)
mStats.mainController?.callSegueFromCell2(myData: myData)
The delegate4 above, which is commented out, is set in the cell header classes as follows:
protocol MyCustomCellDelegator3 {
func callSegueFromCell2(myData dataobject: AnyObject)
}
class PendingNotificationsHeader: UITableViewHeaderFooterView {
var delegate4:MyCustomCellDelegator3!
var MainTableViewController: MainTableViewController?
override init(reuseIdentifier: String?) {
super.init(reuseIdentifier: reuseIdentifier)
setupViews()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
let nameLabel: UILabel = {
let label = UILabel()
label.text = "Pending"
label.font = UIFont.systemFont(ofSize: 17)
label.textColor = .white
return label
}()
let priceAlertButton: UIButton = {
let button = UIButton(type: .system)
button.setTitle("Add new", for: .normal)
button.titleLabel?.font = UIFont (name: "Helvetica", size: 15)
button.setTitleColor(.white, for: .normal)
button.addTarget(self,action: #selector(createNewPriceAlertButtonPressed), for: .touchUpInside)
return button
}()
#IBAction func createNewPriceAlertButtonPressed(_ sender: Any) {
print ("##### New Price Alert Label Pressed")
// Get the view
var mydata = self
//delegate4.callSegueFromCell2(myData: myData)
mStats.mainController?.callSegueFromCell2(myData: myData)
}
Appreciate any guidance.
let pendingCell = pendingNotificationCustomCell()
looks odd.
You should set up your cells in tableView(cellForRow:at:) UITableViewDataSource method.
The issue related to setting the delegate on the cell and not passing it on to the section header. I ended up solving the issue as follows:
I implemented a function and delegate in the MainViewController called API_Segue_01
final class MainTableViewController: UITableViewController, API_Segue_01 {
func API_Segue_01(myData dataobject: AnyObject) {
navigationItem.backBarButtonItem = UIBarButtonItem(title: "Back", style: .plain, target: nil, action: nil)
self.performSegue(withIdentifier: "showCreateNotificationsViewController", sender:dataobject )
}
/// Rest of class code
/// ...
In the custom cell I created a header prototype to reference the external function:
protocol API_Segue_01 {
func API_Segue_01(myData dataobject: AnyObject)
}
I defined a var to hold the reference to the MainTableViewController in the custom cell and associated header
class pendingNotificationCustomCell: UITableViewCell, UITableViewDataSource, UITableViewDelegate {
var funcCallTo: API_Segue_01!
/// Rest of class code
/// ...
class PendingNotificationsHeader: UITableViewHeaderFooterView {
var funcCallTo:API_Segue_01!
/// Rest of class code
/// ...
When creating an instance of the custom cell I provided a reference to the MainTableViewController
pendingCell.funcCallTo = mainTableViewController
As the action button is located in the cell header I passed the reference to the mainTableViewController within the viewForHeaderInSection as follows
func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
print("##### viewForHeaderInSection")
let header = tableView.dequeueReusableHeaderFooterView(withIdentifier: "pendingHeaderID") as! PendingNotificationsHeader
header.funcCallTo = self.funcCallTo
return header
}
I was then able to use this reference to call the API_Segue_01 function
#IBAction func createNewPriceAlertButtonPressed(_ sender: Any) {
print ("##### New Price Alert Label Pressed")
// Get the view
var mydata = self /
funcCallTo.API_Segue_01(myData: mydata)
}
I have two screens the first one"ViewController" has tableview and the second "MapViewController" one contains MKMapView when i navigate from the first one to the second the second one looks like black screen the second screen is supposed to appear the apple map can any one help me ?
import UIKit
import MapKit
struct Category {
let place : String
let coordinates : [Double]
}
class ViewController: UIViewController {
private let tableview : UITableView={
let table = UITableView()
table.register(UITableViewCell.self, forCellReuseIdentifier: "cell")
return table
}()
private let data : [Category]=[
Category(place: "Misr bank ", coordinates: [30.576352,31.503955])
]
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(tableview)
tableview.delegate=self
tableview.dataSource=self
}
override func viewDidLayoutSubviews() {
tableview.frame=view.bounds
}
}
extension ViewController:UITableViewDelegate{
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.deselectRow(at: indexPath, animated: true)
let category = data[indexPath.row]
let mapview = MapViewController(coor: category.coordinates)
self.navigationController?.pushViewController(mapview, animated: true)
}
}
MapViewController
import UIKit
import MapKit
class MapViewController: UIViewController {
#IBOutlet weak var map: MKMapView!
private let coor : [Double]
init(coor:[Double]){
self.coor=coor
super.init(nibName: nil, bundle: nil)
let lat = coor[0]
let long = coor[1]
print(lat)
print(long)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func viewDidLoad() {
super.viewDidLoad()
}
It looks like MapViewController is designed in the storyboard. (If it were not, you would not be talking about #IBOutlet weak var map: MKMapView!)
But if so, then saying MapViewController(coor: category.coordinates) is not how to retrieve the instance that you designed in the storyboard. If you say that, you will indeed get basically an empty interface.
Instead, give that instance in the storyboard an identifier, and in your code, ask the storyboard to instantiate the view controller that has that identifier.
You will also have to rearrange your initialization completely. The storyboard is going to call init(coder:), not init(coor:). In fact, you can get rid of the latter entirely:
#IBOutlet weak var map: MKMapView!
var coor = [Double]()
required init?(coder: NSCoder) {
super.init(coder:coder)
}
override func viewDidLoad() {
super.viewDidLoad()
let lat = coor[0]
let long = coor[1]
print(lat)
print(long)
}
Now when you instantiate MapViewController from the storyboard, immediately also set mapView.coor before pushing.
I create a framework with Custom View. When I'm try to load this to simulator, it loads but when there's a bug when load in deferent iPhone simulators.
I set the constraints to 0, but the custom view doesn't load in all view.
framework code:
#IBDesignable
public class SummaryHeaderView: UIView {
/* View */
#IBOutlet public var view: UIView!
public override init(frame: CGRect) {
super.init(frame: frame)
setUpNib()
}
required public init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setUpNib()
}
internal func setUpNib() {
view = loadNib()
view.frame = bounds
view.autoresizingMask = [.flexibleWidth, .flexibleHeight]
addSubview(view)
}
internal func loadNib() -> UIView {
let bundle = Bundle(for: type(of: self))
let nib = UINib(nibName: String(describing: SummaryHeaderView.self), bundle: bundle)
guard let summaryHeaderView = nib.instantiate(withOwner: self, options: nil).first as? UIView else {
return UIView()
}
return summaryHeaderView
}
}
app code:
#IBOutlet weak var myView: UIView!
var summary = SummaryHeaderView()
override func viewDidLoad() {
super.viewDidLoad()
summary = SummaryHeaderView(frame: self.myView.bounds)
myView.addSubview(summary)
}
Result:
One serious problem with your code is this:
override func viewDidLoad() {
super.viewDidLoad()
summary = SummaryHeaderView(frame: self.myView.bounds)
}
You are assuming here that self.myView.bounds is known at the time viewDidLoad runs. That assumption is wrong. viewDidLoad is too soon for that. Therefore your summary header view shows at the wrong size.
A simple solution would be: instead of using absolute frames everywhere, use auto layout constraints everywhere. That way, all your views and subviews will adjust themselves automatically no matter how big the screen turns out to be.
Another way to do this is to wait until the size of the view is known. For example, move your code into viewDidLayoutSubviews. But then you must take extra steps so that you don't add the subview again later, because viewDidLayoutSubviews can be called many times during the app's lifetime.
Actually i have a Custom view with two button, and i want to hide it at runtime through UIViewController , So i don't get any exact thing to hide that button from UIViewcontroller class
Here is my CustomView class,
import UIKit
class BottomButtonUIView: UIView {
#IBOutlet weak var btnNewOrder: UIButton!
#IBOutlet weak var btnChat: UIButton!
override func awakeFromNib() {
super.awakeFromNib()
}
// MARK: init
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)!
if self.subviews.count == 0 {
setup()
}
}
override init(frame: CGRect) {
super.init(frame: frame)
setup()
}
func setup() {
if let view = Bundle.main.loadNibNamed("BottomButtonUIView", owner: self, options: nil)?.first as? BottomButtonUIView {
view.frame = bounds
view.autoresizingMask = [.flexibleWidth, .flexibleHeight]
addSubview(view)
}
}
#IBAction func btnOrderNowClick(_ sender: Any) {
let VC1 = StoryBoardModel.orderDeatalStoryBord.instantiateViewController(withIdentifier: "NewOrderViewController") as! NewOrderViewController
VC1.isPush = false
let navController = UINavigationController(rootViewController: VC1) // Creating a navigation controller with VC1 at the root of the navigation stack.
let currentController = getCurrentVC.getCurrentViewController()
currentController?.present(navController, animated:true, completion: nil)
}
#IBAction func btnChatNowClick(_ sender: Any) {
}
func getCurrentViewController() -> UIViewController? {
if let rootController = UIApplication.shared.keyWindow?.rootViewController {
var currentController: UIViewController! = rootController
while( currentController.presentedViewController != nil ) {
currentController = currentController.presentedViewController
}
return currentController
}
return nil
}
}
I set it to UIView in StoryBoard, and then I create outlet of that view,
#IBOutlet weak var viewBottmNewOrder: BottomButtonUIView!
Now i want to hide btnNewOrder from UIViewcontroller class but when i use
viewBottmNewOrder.btnNewOrder.isHidden = true it cause null exception, Please do need full answer.
Please don't do like that. The required init(coder aDecoder: NSCoder) will call a lot of times when the BottomButtonUIView created from xib. And your custom view will look like:
[BottomButtonUIView [ BottomButtonUIView [btnNewOrder, btnChat]]].
So when you access to btnNewOrder like that:
viewBottmNewOrder.btnNewOrder it will null.
I think you should add your custom view in viewDidLoad of your `UIViewController'.
I will explain the process before asking my question:
I have a tableviewcontroller, but i needed a textfield was always present at the hearing, to solve this problem i create a xib file with textfield inside and call the view with viewForHeaderInSection; at this point all is perfect.
i call the view whith this code:
override func tableView(tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
return NSBundle.mainBundle().loadNibNamed("createComment", owner: nil, options: nil)[0] as? createComment
}
My problem is next:
I want that when capturing text field starts a new window opens, something like what makes facebook to create a comment.
My code in createComment is:
class createComment: UIView, UITextFieldDelegate {
#IBOutlet weak var commentTextField: StyleComentTextField!
weak var controller:tableComentVC!
var currentController = tableComentVC()
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
override func awakeFromNib() {
self.commentTextField.delegate = self
}
func textFieldShouldBeginEditing(textField: UITextField) -> Bool {
commentTextField.resignFirstResponder()
if(textField == commentTextField){
print("ok i tab in commentTextField")
let storyBoard: UIStoryboard = UIStoryboard (name: "Main", bundle: nil)
self.controller?.navigationController!.pushViewController(storyBoard.instantiateViewControllerWithIdentifier("writeComment") as! writeComment, animated: true)
resign()
return false
}else{
return true
}
}
}
but this code dont work, the print in code works correctly, but i don't know why dont open the next viewcontroller.
Thanks!