-
-
Save byJeevan/9b72128ed97a1dd80a389d683739b6f4 to your computer and use it in GitHub Desktop.
Swift iOS Code Recipes
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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() | |
} | |
} | |
//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 ""
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Safe array:
let data = [1, 3, 4]
data[safe: 1] // Optional(3)
data[safe: 10] // nil