-
-
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() | |
| } | |
| } | |
//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 ""
Uh oh!
There was an error while loading. Please reload this page.