-
-
Save byJeevan/9b72128ed97a1dd80a389d683739b6f4 to your computer and use it in GitHub Desktop.
Swift iOS Recipes | |
================== | |
> https://github.com/JohnSundell/SwiftTips | |
** GUI Layout ** | |
//Avoid Table view having Textfield covered by Keyboard | |
override func viewWillAppear(_ animated: Bool) { | |
super.viewWillAppear(animated) | |
NotificationCenter.default.addObserver(self, selector: #selector(self.keyboardWillShow), name: .UIKeyboardWillShow, object: nil) | |
NotificationCenter.default.addObserver(self, selector: #selector(self.keyboardWillHide), name: .UIKeyboardWillHide, object: nil) | |
view.endEditing(true) | |
} | |
@objc func keyboardWillShow(_ notification: Notification) { | |
let keyboardSize: CGSize? = (notification.userInfo?[UIKeyboardFrameEndUserInfoKey] as AnyObject).cgRectValue?.size | |
let animationDuration = (notification.userInfo?[UIKeyboardAnimationDurationUserInfoKey]) as? CGFloat | |
UIView.animate(withDuration: TimeInterval(animationDuration!), animations: {() -> Void in | |
self.tableView.contentInset = UIEdgeInsetsMake(0.0, 0.0, (keyboardSize?.height)!, 0.0) | |
self.tableView.scrollIndicatorInsets = UIEdgeInsetsMake(0.0, 0.0, (keyboardSize?.height)!, 0.0) | |
}) | |
} | |
@objc func keyboardWillHide(_ notification: Notification) { | |
let animationDuration = (notification.userInfo?[UIKeyboardAnimationDurationUserInfoKey]) as? CGFloat | |
UIView.animate(withDuration: TimeInterval(animationDuration!), animations: {() -> Void in | |
self.tableView.contentInset = UIEdgeInsets.zero | |
self.tableView.scrollIndicatorInsets = UIEdgeInsets.zero | |
}) | |
} | |
override func viewWillDisappear(_ animated: Bool) { | |
super.viewWillDisappear(animated) | |
NotificationCenter.default.removeObserver(self, name: .UIKeyboardWillShow, object: nil) | |
NotificationCenter.default.removeObserver(self, name: .UIKeyboardWillHide, object: nil) | |
} | |
///End/// | |
//DismissView - when dialog kind of presented and tapped out side of box. Target view usually the container/parent view of dialog. | |
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) { | |
if let touch = touches.first, !<target view>.bounds.contains(touch.location(in: <target view>)) { | |
//do dismissal call | |
} | |
} | |
//Keyboard shows over textfield when dialog appears | |
//in viewDidLoad | |
NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillShow), name: NSNotification.Name.UIKeyboardWillShow, object: nil) | |
NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillHide), name: NSNotification.Name.UIKeyboardWillHide, object: nil) | |
//Add these selectors | |
@objc func keyboardWillShow(notification: NSNotification) { | |
if !isKeyboardAppear { | |
if let keyboardSize = (notification.userInfo?[UIKeyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue { | |
if self.view.frame.origin.y == 0{ | |
self.view.frame.origin.y -= keyboardSize.height | |
} | |
} | |
isKeyboardAppear = true | |
} | |
} | |
@objc func keyboardWillHide(notification: NSNotification) { | |
if isKeyboardAppear { | |
if let keyboardSize = (notification.userInfo?[UIKeyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue { | |
if self.view.frame.origin.y != 0{ | |
self.view.frame.origin.y += keyboardSize.height | |
} | |
} | |
isKeyboardAppear = false | |
} | |
} | |
//Alerts | |
var alert = UIAlertController(title: "Alert", message: "Message", preferredStyle: UIAlertControllerStyle.Alert) | |
alert.addAction(UIAlertAction(title: "Click", style: UIAlertActionStyle.Default, handler: nil)) | |
self.presentViewController(alert, animated: true, completion: nil) | |
//Buttons on navigation | |
let newButton = UIBarButtonItem(barButtonSystemItem: .Edit, target: self, action: "doSomething:") | |
self.navigationItem.leftBarButtonItem = newButton | |
### Print as JSON by given Dictionary or [AnyHashable:Any] | |
``` | |
if let theJSONData = try? JSONSerialization.data( | |
withJSONObject: <Your Dictionary>, | |
options: [.prettyPrinted]) { | |
let theJSONText = String(data: theJSONData, | |
encoding: .ascii) | |
print("JSON string = \(theJSONText!)") | |
} | |
``` | |
### for Objective-C : | |
``` | |
NSError *jerror; | |
NSData *jsonData = [NSJSONSerialization dataWithJSONObject:responseData | |
options:NSJSONWritingPrettyPrinted // Pass 0 if you don't care about the readability of the generated string | |
error:&jerror]; | |
if (! jsonData) { | |
NSLog(@"Got an error: %@", jerror); | |
} else { | |
NSString *jsonString = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding]; | |
NSLog(@"JSON >> %@", jsonString); | |
} | |
``` | |
// Table View inside Table View Cell issue | |
// - Outer cell will not expand as per inner table view height. | |
// PS: Make InnerTableView scrolling false. | |
class InnerTableView: UITableView { | |
override var intrinsicContentSize: CGSize { | |
self.layoutIfNeeded() | |
return self.contentSize | |
} | |
override var contentSize: CGSize { | |
didSet{ | |
self.invalidateIntrinsicContentSize() | |
} | |
} | |
} | |
// Button Quick for test | |
let button = UIButton.buttonWithType(UIButtonType.System) as UIButton | |
button.frame = CGRectMake(100, 100, 100, 50) | |
button.backgroundColor = UIColor.greenColor() | |
button.setTitle("Test Button", forState: UIControlState.Normal) | |
button.addTarget(self, action: "buttonAction:", forControlEvents: UIControlEvents.TouchUpInside) | |
button.layer.cornerRadius = 5 | |
button.layer.borderWidth = 1 | |
button.layer.borderColor = UIColor.blackColor().CGColor | |
self.view.addSubview(button) | |
func buttonAction(sender:UIButton!) { | |
//do something | |
} | |
// Quick Label creation for test | |
newLabel = UILabel(frame: CGRect(x: 20, y: 10, width: 300, height: 200)) | |
newLabel.text = "Tap and hold for settings" | |
newLabel.textColor = UIColor.whiteColor() | |
newLabel.textAlignment = NSTextAlignment.Center | |
newLabel.font = UIFont(name: "HelveticaNeue", size: CGFloat(17)) | |
// Slider | |
var slider = UISlider(frame: CGRectMake(0, 0, 300, 200)) | |
slider.addTarget(self, action: "changeSomething:", forControlEvents: UIControlEvents.ValueChanged) | |
slider.backgroundColor = UIColor.blackColor() | |
slider.minimumValue = 0.0 | |
slider.maximumValue = 1.0 | |
slider.continuous = true | |
slider.value = 0.5 | |
self.view.addSubview(slider) | |
func changeSomething(sender:UISlider!) { | |
self.someValue = sender.value | |
} | |
//Dimensions | |
var screenSize: CGRect = UIScreen.mainScreen().bounds | |
//Collection View | |
let layout: UICollectionViewFlowLayout = UICollectionViewFlowLayout() | |
layout.sectionInset = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0) | |
layout.itemSize = CGSize(width: 120, height: 90) | |
collectionView = UICollectionView(frame: self.view.frame, collectionViewLayout: layout) | |
collectionView!.dataSource = self | |
collectionView!.delegate = self | |
collectionView!.registerClass(UICollectionViewCell.self, forCellWithReuseIdentifier: reuseIdentifier) | |
collectionView!.backgroundColor = UIColor.blackColor() | |
self.view.addSubview(collectionView!) | |
//Table View | |
tableView = UITableView(frame: self.view.frame, style: .Plain) | |
self.view.addSubview(tableView) | |
tableView.delegate = self | |
tableView.dataSource = self | |
tableView.registerClass(UITableViewCell.self, forCellReuseIdentifier: cellIdentifier) | |
//Image View | |
var frame = self.view.frame | |
someImage = UIImageView(frame: CGRect(x: 0, y: 0, width: frame.size.width, height: frame.size.height)) | |
//Transitions | |
Pushes | |
let vc = SettingsViewController() | |
self.navigationController?.pushViewController(vc, animated: true) | |
Segues (have to use IB to set up segue identifiers) | |
//Constraints with Masonry Snap | |
let padding = UIEdgeInsetsMake(10, 10, 10, -50) | |
let superview = self.view | |
someView.snp_makeConstraints { make in | |
make.top.equalTo(superview.snp_top).with.offset(padding.top) | |
make.left.equalTo(superview.snp_left).with.offset(padding.left) | |
} | |
/** GUI Events ** | |
//Gray Activity Indicator View | |
var activityIndicatorView = UIActivityIndicatorView(activityIndicatorStyle: .Gray) | |
view.addSubview(activityIndicatorView) | |
activityIndicatorView.center = view.center | |
activityIndicatorView.startAnimating() | |
activityIndicatorView.removeFromSuperview() | |
//Touches | |
press = UILongPressGestureRecognizer(target: self, action: "doSomething:") | |
press.minimumPressDuration = 0.5 | |
press.numberOfTapsRequired = 0 | |
press.numberOfTouchesRequired = 1 | |
press.delegate = self | |
self.view.addGestureRecognizer(press) | |
//Notifications | |
NSNotificationCenter.defaultCenter().addObserver( | |
self, | |
selector: "doSomething:", | |
name: someKey, | |
object: nil) | |
func doSomething(notification: NSNotification) { | |
} | |
//App Delegate setup | |
let rootViewController = SomeViewController() | |
let frame = UIScreen.mainScreen().bounds | |
self.window = UIWindow(frame: frame) | |
let navController = UINavigationController(rootViewController: rootViewController) | |
self.window?.rootViewController = navController | |
self.window?.backgroundColor = UIColor.whiteColor() | |
self.window?.makeKeyAndVisible() | |
//Delegates | |
protocol NewDelegate { | |
func doSomething() | |
} | |
In class: | |
var delegate: NewDelegate? | |
self.delegate?.doSomething() | |
In target class: | |
Inherit from NewDelegate | |
newObject.delegate = self | |
Implement the protocol methods | |
// ****** Loads Xib/nib from project as uiviewcontroller ****/ | |
extension UIViewController { | |
static func loadFromNib() -> Self { | |
func instantiateFromNib<T: UIViewController>() -> T { | |
return T.init(nibName: String(describing: T.self), bundle: nil) | |
} | |
return instantiateFromNib() | |
} | |
} | |
/*****Loads ViewController by all ways : ***/ | |
extension UIViewController { | |
static func instantiate<TController: UIViewController>(_ storyboardName: String) -> TController { | |
return instantiateFromStoryboardHelper(storyboardName) | |
} | |
static func instantiate<TController: UIViewController>(_ storyboardName: String, identifier: String) -> TController { | |
return instantiateFromStoryboardHelper(storyboardName, identifier: identifier) | |
} | |
fileprivate static func instantiateFromStoryboardHelper<T: UIViewController>(_ name: String, identifier: String? = nil) -> T { | |
let storyboard = UIStoryboard(name: name, bundle: Bundle(for: self)) | |
return storyboard.instantiateViewController(withIdentifier: identifier ?? String(describing: self)) as! T | |
} | |
static func instantiate<TController: UIViewController>(xibName: String? = nil) -> TController { | |
return TController(nibName: xibName ?? String(describing: self), bundle: Bundle(for: self)) | |
} | |
} | |
/ ***** End Load a ViewController ***** / | |
extension String { | |
func replaceOccuranceOfSpaceInURLString() -> String { | |
return self.addingPercentEncoding(withAllowedCharacters: CharacterSet.urlQueryAllowed) ?? self | |
} | |
var boolValue: Bool { | |
return (self as NSString).boolValue | |
} | |
var isBackSpace:Bool { | |
let char = self.cString(using: String.Encoding.utf8)! | |
let isBackSpace = strcmp(char, "\\b") | |
return isBackSpace == -92 | |
} | |
} | |
// Storing a property in a class using Static property. | |
extension UIView { | |
struct Holder { | |
static var _padding:[UIView:UIEdgeInsets] = [:] | |
} | |
var padding : UIEdgeInsets { | |
get{ return UIView.Holder._padding[self] ?? .zero} | |
set { UIView.Holder._padding[self] = newValue } | |
} | |
} | |
//Button Extension that makes button with icon at top | |
// -----[image]----------- | |
// ----title of button---- | |
// ----------------------- | |
extension UIButton { | |
func centerVertically(padding: CGFloat = 6.0) { | |
guard | |
let imageViewSize = self.imageView?.frame.size, | |
let titleLabelSize = self.titleLabel?.frame.size else { | |
return | |
} | |
let totalHeight = self.bounds.size.height | |
self.imageEdgeInsets = UIEdgeInsets( | |
top: max(0, -(totalHeight - imageViewSize.height)), | |
left: 0.0, | |
bottom: 0.0, | |
right: -titleLabelSize.width | |
) | |
self.titleEdgeInsets = UIEdgeInsets( | |
top: 0.0, | |
left: -imageViewSize.width, | |
bottom: -(totalHeight - titleLabelSize.height), | |
right: 0.0 | |
) | |
self.contentEdgeInsets = UIEdgeInsets( | |
top: 0.0, | |
left: 0.0, | |
bottom: titleLabelSize.height, | |
right: 0.0 | |
) | |
} | |
} | |
//Extension - shortcut to add child vc | |
extension UIViewController { | |
func add(_ child: UIViewController) { | |
addChild(child) | |
view.addSubview(child.view) | |
child.didMove(toParent: self) | |
} | |
func remove() { | |
// Just to be safe, we check that this view controller | |
// is actually added to a parent before removing it. | |
guard parent != nil else { | |
return | |
} | |
willMove(toParent: nil) | |
view.removeFromSuperview() | |
removeFromParent() | |
} | |
} | |
SingleTon
@objc
class RestoreScreenManager : NSObject {
private override init() { }
static let shared: RestoreScreenManager = {
let instance = RestoreScreenManager()
// Setup code if any
return instance
}()
}
extension UIView {
/** Loads instance from nib with the same name. */
func loadNib() -> UIView {
let bundle = Bundle(for: type(of: self))
let nibName = type(of: self).description().components(separatedBy: ".").last!
let nib = UINib(nibName: nibName, bundle: bundle)
return nib.instantiate(withOwner: self, options: nil).first as! UIView
}
}
extension UIViewController {
static func loadFromNib() -> Self {
func instantiateFromNib<T: UIViewController>() -> T {
return T.init(nibName: String(describing: T.self), bundle: nil)
}
return instantiateFromNib()
}
}
//1.Create UIView extension
extension UIView {
class func initFromNib<T: UIView>() -> T {
return Bundle.main.loadNibNamed(String(describing: self), owner: nil, options: nil)?[0] as! T
}
}
//2. Create MyCustomView
class MyCustomView: UIView {
@IBOutlet weak var messageLabel: UILabel!
static func instantiate(message: String) -> MyCustomView {
let view: MyCustomView = initFromNib()
view.messageLabel.text = message
return view
}
}
Safe array:
extension Collection {
subscript (safe index: Index) -> Element? {
return indices.contains(index) ? self[index] : nil
}
}
let data = [1, 3, 4]
data[safe: 1] // Optional(3)
data[safe: 10] // nil
//Fit view to parent
extension UIView {
func pinEdges(to other: UIView) {
leadingAnchor.constraint(equalTo: other.leadingAnchor).isActive = true
trailingAnchor.constraint(equalTo: other.trailingAnchor).isActive = true
topAnchor.constraint(equalTo: other.topAnchor).isActive = true
bottomAnchor.constraint(equalTo: other.bottomAnchor).isActive = true
}
}
func takeScreenshot(view: UIView) -> UIImageView {
UIGraphicsBeginImageContext(view.frame.size)
view.layer.renderInContext(UIGraphicsGetCurrentContext())
let image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
UIImageWriteToSavedPhotosAlbum(image, nil, nil, nil)
return UIImageView(image: image)
}
public extension UITableView {
/**
Register nibs faster by passing the type - if for some reason the `identifier` is different then it can be passed
- Parameter type: UITableViewCell.Type
- Parameter identifier: String?
*/
func registerCell(type: UITableViewCell.Type, identifier: String? = nil) {
let cellId = String(describing: type)
register(UINib(nibName: cellId, bundle: nil), forCellReuseIdentifier: identifier ?? cellId)
}
func registerHeaderFooterView<T: UITableViewHeaderFooterView>(_ : T.Type) {
register(UINib(nibName: T.viewIdentifier, bundle: nil), forHeaderFooterViewReuseIdentifier: T.viewIdentifier)
}
/**
DequeueCell by passing the type of UITableViewCell
- Parameter type: UITableViewCell.Type
*/
func dequeueCell<T: UITableViewCell>(withType type: UITableViewCell.Type) -> T? {
return dequeueReusableCell(withIdentifier: type.cellIdentifier) as? T
}
/**
DequeueCell by passing the type of UITableViewCell and IndexPath
- Parameter type: UITableViewCell.Type
- Parameter indexPath: IndexPath
*/
func dequeueCell<T: UITableViewCell>(withType type: UITableViewCell.Type, for indexPath: IndexPath) -> T? {
return dequeueReusableCell(withIdentifier: type.cellIdentifier, for: indexPath) as? T
}
func dequeueReusableCell<T: UITableViewCell>(forIndexPath indexPath: IndexPath) -> T {
guard let cell = dequeueReusableCell(withIdentifier: T.cellIdentifier, for: indexPath) as? T else {
fatalError("Could not dequeue cell with identifier: \(T.cellIdentifier)")
}
return cell
}
func dequeueHeaderFooterView<T: UITableViewHeaderFooterView>() -> T {
guard let view = dequeueReusableHeaderFooterView(withIdentifier: T.viewIdentifier) as? T else {
fatalError("Could not dequeue cell with identifier: \(T.viewIdentifier)")
}
return view
}
}
public extension UITableViewCell {
static var cellIdentifier: String {
return String(describing: self)
}
}
extension UITableViewHeaderFooterView {
static var viewIdentifier: String {
return String(describing: self)
}
}
Shadow for CollectionView Cell > content view .
`extension UICollectionViewCell {
func shadowDecorate() {
let radius: CGFloat = 10
self.contentView.layer.cornerRadius = radius
// Always mask the inside view
self.contentView.layer.masksToBounds = true
self.layer.shadowColor = UIColor.black.cgColor
self.layer.shadowOffset = CGSize(width: 0, height: 1.0)
self.layer.shadowRadius = 3.0
self.layer.shadowOpacity = 0.5
// Never mask the shadow as it falls outside the view
self.layer.masksToBounds = false
// Matching the contentView radius here will keep the shadow
// in sync with the contentView's rounded shape
self.layer.cornerRadius = radius
}
}
`
Codable
let decoder = JSONDecoder()
do {
let model = try decoder.decode(TestModel.self, from: data)
//do something with model ...
} catch {
print(error)
}
//Updated :
static func parse<T: Codable>(_ model: T.Type,
from apiResponse: [AnyHashable: Any]) -> T? {
if let jsonData = try? JSONSerialization.data(withJSONObject: apiResponse, options: .prettyPrinted) {
let decoder = JSONDecoder()
do {
return try decoder.decode(model.self, from: jsonData)
}
catch let error {
print("Parsing Error : \(error)")
}
}
return nil
}
class SelfSizedTableView: UITableView {
var maxHeight: CGFloat = UIScreen.main.bounds.size.height
override func reloadData() {
super.reloadData()
self.invalidateIntrinsicContentSize()
self.layoutIfNeeded()
}
override var intrinsicContentSize: CGSize {
let height = min(contentSize.height, maxHeight)
return CGSize(width: contentSize.width, height: height)
}
}
Note: Another aspect to highlight is since our auto layout constraints don’t define the height of the table view, you might see missing constraintswarnings: This warning is easy to fix. Go to the size inspector and set Intrinsic size -> Placeholder:
Ref: https://dushyant37.medium.com/swift-4-recipe-self-sizing-table-view-2635ac3df8ab
Read JSON from Local
static func readJSONFromFile(fileName: String) -> Any?
{
var json: Any?
if let path = Bundle.main.path(forResource: fileName, ofType: "json") {
do {
let fileUrl = URL(fileURLWithPath: path)
// Getting data from JSON file using the file URL
let data = try Data(contentsOf: fileUrl, options: .mappedIfSafe)
json = try? JSONSerialization.jsonObject(with: data)
} catch {
// Handle error here
}
}
return json
}
//Print all the installed font
for family in UIFont.familyNames {
print("family:", family)
for font in UIFont.fontNames(forFamilyName: family) {
print("font:", font)
}
}
Line Spacing of UILabel
extension UILabel {
var customLineSpace: CGFloat {
get {
return 0
}
set {
let textAlignment = self.textAlignment
let paragraphStyle = NSMutableParagraphStyle()
paragraphStyle.lineSpacing = newValue
let attributedString = NSAttributedString(string: self.text ?? "", attributes: [NSAttributedString.Key.paragraphStyle: paragraphStyle])
self.attributedText = attributedString
self.textAlignment = textAlignment
}
}
}
Usage : myLabel.customLineSpace = 14.0
Detect Scrollview reached to End or Top
extension UIScrollView {
var scrolledToTop: Bool {
let topEdge = 0 - contentInset.top
return contentOffset.y <= topEdge
}
var scrolledToBottom: Bool {
let bottomEdge = contentSize.height + contentInset.bottom - bounds.height
return contentOffset.y >= bottomEdge
}
}
User Default extension simplified:
extension UserDefaults {
var onboardingCompleted: Bool {
get { return bool(forKey: #function) }
set { set(newValue, forKey: #function) }
}
var currentLanguageCode: String {
get { return string(forKey: #function) ?? "en" }
set { set(newValue, forKey: #function) }
}
var highScore: Int {
get { return integer(forKey: #function) }
set { set(newValue, forKey: #function) }
}
}
let networkInfo = CTTelephonyNetworkInfo()
let carrierTypeString = networkInfo.serviceCurrentRadioAccessTechnology!.values.first!
switch carrierTypeString {
case CTRadioAccessTechnologyGPRS,CTRadioAccessTechnologyEdge,CTRadioAccessTechnologyCDMA1x: return "2G"
case CTRadioAccessTechnologyWCDMA,CTRadioAccessTechnologyHSDPA,CTRadioAccessTechnologyHSUPA,CTRadioAccessTechnologyCDMAEVDORev0,CTRadioAccessTechnologyCDMAEVDORevA,CTRadioAccessTechnologyCDMAEVDORevB,CTRadioAccessTechnologyeHRPD: return "3G"
case CTRadioAccessTechnologyLTE: return "4G"
default: return ""
/*Dependency injection of View Model - when a view controller instantiating from XIB nib */
//view controller
// Instantiating