• Skip to main content
  • Skip to primary sidebar

Ravi Shankar

Tweaking Apps

  • Swift
  • Tech Tips

CoreData tutorial in Swift 5 using NSFetchedResultsController

December 25, 2019 By Ravi Shankar 38 Comments

Let us see an example TaskManager app using CoreData written in Swift Programming language. This app provides the following functionality

  • Allow users to enter new task
  • Update existing task
  • delete task

TaskManager using CoreData201407121030.jpg

You can download source code for this project from Github. If you are familiar with user interface then move on to the Core Data implementation in Swift section

Create a new File -> New -> Project and select template Single View Application

201407121032.jpg

Enter Product Name, Language as Swift and select “Use Core Data” for the new Project.

201407121037.jpg

User Interface Implementation

 

Add new TableViewController for managing tasks

 

Delete ViewController.swift and Add new view controller which will be used for displaying the list of tasks.

201407121059.jpg

Right click on the Project and select New File

201407121101.jpg

Choose the template as Cocoa Touch under iOS -> Source

201407121103.jpg

Enter name of the file as TaskManagerViewController with Subclass as UITableViewController and Language as Swift.

201407121104.jpg

Add new UITableViewController to the Storyboard

 

Navigate to Main.storyboard, delete the ViewController and add new TableViewController to the Storyboard

201407121108.jpg

Embed this TableViewController inside a navigation controller. You can do this by clicking Editor menu -> Embed In -> Navigation Controller.

201407121110.jpg

Navigate to Storyboard file and select Table View Controller. Click Identity Inspector and set the Class to the TaskManagerViewController.

Add button to the navigation bar

 

Users can navigate to new task screen by tapping the button on the TaskManager. Drag and drop the button bar item to the navigation bar.

201407121116.jpg

In the attributes inspector, set the identifier for button bar item as Add. Also enter title as “Task Manager” in the navigation bar.

Add View Controller for entering task detail

 

Now to enter task detail, let us add new View Controller. From the Objects library, drag and drop View Controller on to storyboard. Then drag and drop a navigation item to this View Controller. Add a Done bar button item for saving the changes, Cancel bar button item for dismissing the screen. Also provide a title for the screen as Task Detail.

201407121138.jpg

Then add a textfield to the View Controller to enter details about the task.

201407121540.jpg

Now to connect the Task Manager screen to Task Detail screen, select the Add button in Task Manager screen, hold the control button and make a connection to the Task Detail screen. Select type of Action Segue as Show.

201407121125.jpg201407121127.jpg

Select each View Controller and Click on the Resolve Auto Layout Issues option and pick Reset to Suggested Constraints. This would ensure that the controls alignment and display remains same in different screen sizes.

 

201407121557.jpg

Add View Controller Class

Right click on the Project, select New File from menu list.

201407130919.jpg

Select Cocoa Touch Class as template.

201407130920.jpg

Enter the Class name as TaskDetailViewController, Subclass of UIViewController and Language as Swift.

201407130921.jpg

Navigate to Storyboard file and select Task Detail View Controller. Click Identity Inspector and set the Class to the TaskDetailViiewController.

201407141329.jpg

Now let us add the function required for dismissing the View Controller. This gets called when user taps done and cancel buttons. Navigate to TaskDetailViewController.swift file and add the following functions.

    
   @IBAction func done(sender: AnyObject) {
        if task != nil {
            editTask()
        } else {
            createTask()
        }
        dismissViewController()
    }
    
    @IBAction func cancel(sender: AnyObject) {
         dismissViewController()
    }
    
    // MARK:- Dismiss ViewControllers
    
    func dismissViewController() {
        navigationController?.popViewController(animated: true)
    }

 

Connect the IBActions with the corresponding done and cancel buttons

201407130930.jpg

 

Try to compile and run the app on the Simulator. You should see the following Task Manager screen and tapping + sign should display the Task Detail screen with TextField, Cancel and Done button.

201407130933.jpg201407130935.jpg

 

When you tap the Cancel or Done button should take you to the Task Manager screen.

Create IBOutlet element for UITextField Element

Add the following piece of code after the Class TaskDetailViewController: UIViewController

@IBOutlet var txtDesc: UITextField!

Now use the connection inspector to connect the IBOutlet variable to the textfield on the User Interface.

Core Data Implementation

 

Click Show Project Navigator and select CoreData model file (ending with extension .xcdatamodelid)

201407121042.jpg

Click Add Entity option available at the bottom of Xcode Editor window.

201407121045.jpg

Then enter the name of the entity as Tasks.

201407121047.jpg

Click Add Attribute then enter name as desc and choose the type as String. This attribute will be used for storing the task detail information.

201407121049.jpg

Now to generate the CoreData mapping class, click Editor and select Create NSManagedObject Subclass.

201407121051.jpg

Select the data models and click Next

201407121052.jpg

Then select entities and in this example it is Tasks

201407121053.jpg

Make sure to select Language for NSManagedObject class as Swift. Click Create to the create new class.

201407121055.jpg

Click Yes to to configure an Objective-C birding header for the Swift class.

201407121056.jpg

This should create a new class called Tasks.swfit with variable for corresponding attribute defined in Entities

Write code to save new task details

Add the following line at the top of TaskDetailViewController class

  let managedObjectContext = (UIApplication.shared.delegate as! AppDelegate).managedObjectContext

The above line defines a variable to store the ManagedObjectContext. In order to save the task information entered in the UITextField, add the following code to TaskDetailViewController.swift. Then call this function in the @IBAction done.

   func createTask() {
        let entityDescripition = NSEntityDescription.entity(forEntityName: "Tasks", in: managedObjectContext)
        let task = Tasks(entity: entityDescripition!, insertInto: managedObjectContext)
        task.desc = txtDesc.text!
        do {
            try managedObjectContext.save()
        } catch _ {
        }
    }
@IBAction func done(sender: AnyObject) {
	createTask()
	dismissViewController()
}

 

The createTask function uses the Tasks Entity class and ManagedObjectContext class to save the information entered in the text field in to SQLite using CoreData.

Now if you try to run this app in Xcode simulator, you should be able to enter the details in textfield and save the task. But there is no way to see the saved tasks.

Write code to retrieve information from SQLite using NSFetchedResultsController

We will be using NSFetchedResultsController to retrieve and manage information returned from Core Data. Write the following code after the class declaration of TaskManagerViewController class

 let managedObjectContext = (UIApplication.shared.delegate as! AppDelegate).managedObjectContext
    
var fetchedResultController: NSFetchedResultsController = NSFetchedResultsController<NSFetchRequestResult>()
    

201407141923.jpg

 

You might see the above error message “Use of undeclared type ’NSFetchedResultsContoller”. This can be fixed by importing CoreData module and making your class a delegate of NSFetchedResultsControllerDelegate.

import CoreData

class TaskManagerViewController: UITableViewController, NSFetchedResultsControllerDelegate {

Then add the following function to populate fetchedResultController.

  
    func getFetchedResultController() -> NSFetchedResultsController<NSFetchRequestResult> {
        fetchedResultController = NSFetchedResultsController(fetchRequest: taskFetchRequest(),  managedObjectContext: managedObjectContext, sectionNameKeyPath: nil, cacheName: nil)
        return fetchedResultController
    }
      

 

Update the ViewDidLoad method of TableViewController to populate fetchedResultController variable and set the delegate to self and call the function to retrieve the results.

override func viewDidLoad() {
        super.viewDidLoad()

       fetchedResultController = getFetchedResultController()
        fetchedResultController.delegate = self
        do {
            try fetchedResultController.performFetch()
        } catch _ {
        }
    }

Implement the TableView related functions to display the data

Navigate to Main.storyoard then to TaskManagerViewController and set Identifier for Prototype Cells as “Cell”

201407142004.jpg

 

Add the following functions required for population of rows in tableView and to refresh the tableView when content is changed.

 

And for refreshing the tableview content add the following piece of code snippet.

  func controllerDidChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {          tableView.reloadData()
    }

Swift uses namespaces for classes, so the downcast of NSManagedObject class to Tasks class will work only when you append the module name with the class for Entity

  let task = fetchedResultController.object(at: indexPath as IndexPath) as! Tasks

Navigate to TaskManager.xcdatamodeld, select Tasks under Entities and using the Data Model Inspector append Module name i.e TaskManager with the class.

201407142038.jpg

 

 

Now you should be able to see the data added via TaskDetailScreen in the TaskManager Screen.

201407142059.jpg

Implementation of deleting rows from tableView and CoreData entity

 

Copy and paste the following code to provide the users with the option to delete the rows.

 override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {
        let managedObject:NSManagedObject = fetchedResultController.object(at: indexPath as IndexPath) as! NSManagedObject
        managedObjectContext.delete(managedObject)
        do {
            try managedObjectContext.save()
        } catch _ {
        }
    }

201407142116.jpg

Implementation of editing task details

Now let us see how to edit a task in the TaskManager screen. We will re-use the same TaskDetail screen for editing the task information. First let create segue from UITableViewCell to TaskDetailViewController. You can do this by holding the Control key and drag and drop the connection to TaskDetailViewController.

201407142127.jpg

 

Make sure to set the Segue identifier as edit using Attributes Inspector

201407142128.jpg

 

Add the following piece of code in TaskManagerViewController to populate the task detail for the edited row.

      override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
        if segue.identifier == "edit" {
            let cell = sender as! UITableViewCell
            let indexPath = tableView.indexPath(for: cell)
            let taskController:TaskDetailViewController = segue.destination as! TaskDetailViewController
            let task:Tasks = fetchedResultController.object(at: indexPath!) as! Tasks
            taskController.task = task
        }
    }
    

 

Navigate to TaskDetailViewController and variable which used to pass the task details across the ViewControllers

var task: Task? = nil

In viewDidLoad fund, populate the textField with the task details for edit operation i.e task is not nil

 override func viewDidLoad() {
        super.viewDidLoad()
     if task != nil {
            txtDesc.text = task?.desc
        }
    }

 

Then add a new function to save the edited task. Also modify the done function to handle create and edit task functionality

func editTask() {
       task?.desc = txtDesc.text!
        do {
            try managedObjectContext.save()
        } catch _ {
        }
    }
@IBAction func done(sender: AnyObject) {
        if task != nil {
            editTask()
        } else {
            createTask()
        }
        dismissViewController()
    }

 

Now when you run the app in Xcode simulator, you should be able to edit the task.

The final piece of code left is to add background colour to the navigation bar for both the controllers. Click AppDelegate.swift file and add the following function. Then call this function from the didFinishLaunchingWithOptions.

    func setupAppearance() {
        let navigationBarAppearance = UINavigationBar.appearance()
        navigationBarAppearance.barTintColor = UIColor(red: 51.0/255.0, green: 104.0/255.0, blue: 121.0/255.0, alpha: 1.0)
        navigationBarAppearance.tintColor = UIColor.white
        navigationBarAppearance.titleTextAttributes = [NSAttributedString.Key.foregroundColor:UIColor.white]
    }

 

Running the app on the iOS simulator should change the appearance for the navigation bar.

201407142233.jpg

 

Hope you found this beginner tutorial useful. Please use the comments section to share your thoughts and suggestion

Download the source code for this tutorial from GitHub

Filed Under: Develop, ios, iPhone, Programming, Swift, Xcode Tagged With: CoreData, delegation, NSFetchedResultContoller, Swift, Tutorials, UITableView, User Interface, Xcode

Storyboard, Scene and Segue

November 22, 2019 By Ravi Shankar Leave a Comment

Storyboard in iOS is helps you to design the user interface of your App. A storyboard can contain one or more Scenes (View Controllers) and the connection or relationship between two scenes are known as Segue. This is how a typical storyboard with Scenes and Segue look.

The above example storyboard contains two scenes, first and second. These scenes are connected to each other by a Show Segue. Now let us see more about the Storyboard and Segues

Initial ViewController

App needs to know the ViewControler that will be displayed as the initial screen. You can set any controller as the initial scene using the option available as part of the Attributes Inspector

 

The storyboard will display an arrow in front of the Initial View Controller as shown in below screenshot. You can also drag and drop this arrow to the change the Initial View Controller.

Types of Segues

A segue is a relationship set between two view controllers. This can be done by pressing Control then drag and drop from one View Controller to anlther. Listed below are the different types of segues

  • Show 
  • Show Detail
  • Present Modally
  • PopOver Presentation
  • Custom

Check out this excellent Stackoverflow answer on types of Segues

Unwind Segues

Only the Show segue has a back button in the navigation bar of the child controller which takes you back to the Parent controller. For rest of the segues, we can use unwind segues to dismiss the child controller. Unwind segue can be created by first adding a IBAction with parameter type as UIStoryboardSegue to the parent Vuew Controller.

   //MARK:- Unwind Segue
    @IBAction func cancel(segue:UIStoryboardSegue {
        // do nothing
    }

Add a button to the child view controller press Control then drag and drop the button to the Exit button available at the top of the ViewController. Now select the Action Segue which was added to the parent view controller.

Transfering data between ViewControllers

One of the common question that gets asked is “how to transfer data between two view controllers“. Create a property in the child view controller and set the value for this property in the prepareForSegue function of the parent view controller.

//MARK:- ViewController Transition

     override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
        if  segue.identifier  ==   "detail"   {
            let  childViewController  =  segue.destination  as! SecondViewController
            childViewController.studentName  =  nameTextField.text
        }
    }

In the above prepareForSegue function, we check whether segue identifier is equal to detail. The identifier name can be provided under the Storyboard Segue in Attributes Inspector. Then we set the value for studentName property of the SecondViewController. By this way, you can transfer data between the View Controllers,

In the SecondViewController, you should be able access this property value and perform the required action. The below viewDidLoad code snippet access the property value and displays it in a label.

class SecondViewController: UIViewController {

    var studentName:String?
    @IBOutlet var displayName:UILabel!

    override func viewDidLoad() {
        super.viewDidLoad()

        // Do any additional setup after loading the view.
        if let name = studentName {
            displayName.text = name
        }
    }
}

Non Segue Transition

Another alternate way to transition between the View Controllers is to use instantiateViewController withIdentifier function of storyboard

//MARK:- Non Segue
  @IBAction func nonSegueCall(sender: AnyObject) {
        let childViewContoller  =
            storyboard?.instantiateViewController(withIdentifier: "SecondViewController") as! SecondViewController
        childViewContoller . studentName  =  nameTextField.text
        present( childViewContoller ,  animated: true ,  completion: nil)
    }

The above code snippet provides the same behaviour as prepareForSegue function. This function can be called on tap of a button and this requires an identifier to specified for the View Controller.

Download sample code from here.

Filed Under: ios, Xcode Tagged With: prepareForSegue, Scene, Segue, Storyboard

Get your current address in Swift (Swift 5)

November 7, 2019 By Ravi Shankar 22 Comments

In this Swift tutorial, we will see the steps required for using CoreLocation framework and retrieve the latitude and longitude of the location. Then use the CLGeocoder to reverse geocode the latitude and longitude details. This is a very basic tutorial that retrieves the location details on tap of a button and displays the information on the screen.

Updated :- The source has now been to updated to show the address details in TextFields.

Click File -> New and select Project sub menu list.

201407251656.jpg

In the Choose a template screen, select Single View Application and click Next button.

201407251657.jpg

Enter the product name as WhereAmI and select the language as Swift then click Next. Select a folder and create the new project and that folder.

201407251658.jpg

Navigate to ViewController.swift file and add a new function with name as findMyLocation after viewDidLoad function.

@IBAction func findMyLocation(sender: AnyObject) {

}

Navigate to Main.storyboard file, drag and drop Button from the Object Library on to the View Controller. Using the attributes inspector, change the name of the button to “Where Am I?”. If you need then change the background and text colour for the button. Then centre align the button both vertically and horizontally. Use the Resolve Auto Layout Option and pick Reset to Suggested Constraints.

201407251707.jpg

Navigate to Connections Inspector and connect the button to the findMyLocation action under Received Actions (Touch Up Inside event).

201407251711.jpg

Click the Project under Project Navigator and navigate to Build Phases. Click the + sign under Link Binary With Libraries and pick CoreLocation.framework from the list.

Add CoreLocation framework to Swift project201407251718.jpg

CoreLocation in Swift

Now navigate back to ViewController.swift file, add import CoreLocation after UIKit. Then we need to assign the current class as the delegate for CLLocationManagerDelegate. This is required because we will be using couple of delegate methods from CLLocationManager.

class ViewController: UIViewController, CLLocationManagerDelegate {

Define a constant for CLLocationManager after the class declaration.

let locationManager = CLLocationManager()

Navigate to IBAction function findMyLocation and add the following code

locationManager.delegate = self
locationManager.desiredAccuracy = kCLLocationAccuracyBest
locationManager.requestWhenInUseAuthorization()
locationManager.startUpdatingLocation()

The above lines sets the class as delegate for locationManager, specifies the location accuracy and starts receiving location updates from CoreLocation. In order to get the location updates we need to implement the delegate functions of CLLocationManager, didUpdateLocations and didFailWithError

func locationManager(manager: CLLocationManager!, didUpdateLocations locations: [AnyObject]!) {
}

func locationManager(manager: CLLocationManager!, didFailWithError error: NSError!) {
}

didUpdateLocations function is triggered when new location updates are available. Similarly didFailWithError is called when there is a problem receiving the location updates. Let us first start implementing the simpler one i.e didFailWithError function. When there is an error, we are going to log the message to the console.

func locationManager(manager: CLLocationManager!, didFailWithError error: NSError!) {
	println("Error while updating location " + error.localizedDescription)
}

Then update didUpdateLocations function with the following code, also add a new function for printing the location details.

func locationManager(manager: CLLocationManager!, didUpdateLocations locations: [AnyObject]!) {
CLGeocoder().reverseGeocodeLocation(manager.location, completionHandler: {(placemarks, error)->Void in
		if error {
			println("Reverse geocoder failed with error" + error.localizedDescription)
			return
		}

		if placemarks.count > 0 {
			let pm = placemarks[0] as CLPlacemark
			self.displayLocationInfo(pm)
		} else {
			println("Problem with the data received from geocoder")
		}
	})
}

func displayLocationInfo(placemark: CLPlacemark) {
	if placemark != nil {
		//stop updating location to save battery life
		locationManager.stopUpdatingLocation()
		println(placemark.locality ? placemark.locality : "")
		println(placemark.postalCode ? placemark.postalCode : "")
		println(placemark.administrativeArea ? placemark.administrativeArea : "")
		println(placemark.country ? placemark.country : "")
	}
}

in the didUpdateLocations function, we pass the location co-ordinates to

 CLGeocoder().reverseGeocodeLocation

. Then check for error and process the location array (placemarks). Then for displaying the location details, we pass the placemark detail to displayLocationInfo function.

Add key to Info.plist for request permission to User’s location

In order to use the user’s location you need to request permission from the user by adding the keys “NSLocationWhenInUseUsageDescription” or “NSLocationAlwaysUsageDescription” to your Info.plist file, with the value blank or optionally a message included in the prompt to the user. One of those two key is required in iOS 8 to ask for your location.

Testing Location in Simulator

It is not possible to test current location with simulator. So we need to define a custom location or use the location already defined for debug purpose. When an app tries to access the location services on a simulator it displays the following popup.

Current Location Simulator Xcode 6

Here is a workaround to test the app using Xcode 11. Run the app on the a Simulator and tap “Find My Location” button. Nothing will happen as the app has not been given permission to use Location Services on Simulator.

201407251956.jpg

Click Debug menu then Location and select Apple from the sub menu list. Still nothing happens with the app and no messages are written to the console.

201407251957.jpg

Now click Hardware menu and select Home from menu list.

201407251959.jpg

Tap the Settings icon on Simulator -> Privacy -> Location -> Tap , select Always to allow the app to access the location.

201407252003.jpg201407252005.jpg

Now you should see Apple address details in the Console Output.

201407252008.jpg

You can also try out other location using the Simulate Location option.

Simulate location in Xcode 6 simulator

Hopefully the testing should be lot easier after issue with “Allow to use Current Location” is fixed.

Source from GitHub

Filed Under: Apple, Develop, ios, Programming, Xcode Tagged With: Apple, CoreLocation, Swift, Xcode

NSUnknownKeyException – this class is not key value coding-compliant for the key

November 6, 2019 By Ravi Shankar Leave a Comment

NSUnknownKeyException – this class is not key value coding-compliant for the key common error that most of the Swift beginners would face during the learning period.

Terminating app due to uncaught exception ‘NSUnknownKeyException’, reason: ‘[<Handbook.StudentEntryController 0x78f948a0> setValue:forUndefinedKey:]: this class is not key value coding-compliant for the key nametxt.’
*** First throw call stack:
(
0 CoreFoundation 0x02316a14 __exceptionPreprocess + 180
1 libobjc.A.dylib 0x01751e02 objc_exception_throw + 50
2 CoreFoundation 0x02316631 -[NSException raise] + 17
3 Foundation 0x013e71bc -[NSObject(NSKeyValueCoding) setValue:forUndefinedKey:] + 282
4 Foundation 0x0134183a _NSSetUsingKeyValueSetter + 115
5 Foundation 0x013417bf -[NSObject(NSKeyValueCoding) setValue:forKey:] + 295
6 UIKit 0x02cac06d -[UIViewController setValue:forKey:] + 85
7 Foundation 0x0137601d -[NSObject(NSKeyValueCoding) setValue:forKeyPath:] + 384
8 UIKit 0x02f1fcb4 -[UIRuntimeOutletConnection connect] + 132
9 libobjc.A.dylib 0x0176600c -[NSObject performSelector:] + 62
10 CoreFoundation 0x02246f51 -[NSArray makeObjectsPerformSelector:] + 273
11 UIKit 0x02f1e34e -[UINib instantiateWithOwner:options:] + 2102
12 UIKit 0x02cb3abc -[UIViewController _loadViewFromNibNamed:bundle:] + 429
13 UIKit 0x02cb44f4 -[UIViewController loadView] + 189
14 UIKit 0x02f47b66 -[UITableViewController loadView] + 88
15 UIKit 0x02cb4900 -[UIViewController loadViewIfRequired] + 154
16 UIKit 0x02cbb406 -[UIViewController __viewWillAppear:] + 114
17 UIKit 0x02cde5b9 -[UIViewController(UIContainerViewControllerProtectedMethods) beginAppearanceTransition:animated:] + 202
18 UIKit 0x02cf09cc -[UINavigationController _startCustomTransition:] + 1389
19 UIKit 0x02d02769 -[UINavigationController _startDeferredTransitionIfNeeded:] + 803
20 UIKit 0x02d03ada -[UINavigationController __viewWillLayoutSubviews] + 68
21 UIKit 0x02edfc4a -[UILayoutContainerView layoutSubviews] + 252
22 UIKit 0x02bb5008 -[UIView(CALayerDelegate) layoutSublayersOfLayer:] + 810
23 libobjc.A.dylib 0x01766059 -[NSObject performSelector:withObject:] + 70
24 QuartzCore 0x029b480a -[CALayer layoutSublayers] + 144
25 QuartzCore 0x029a84ee _ZN2CA5Layer16layout_if_neededEPNS_11TransactionE + 388
26 QuartzCore 0x029a8352 _ZN2CA5Layer28layout_and_display_if_neededEPNS_11TransactionE + 26
27 QuartzCore 0x0299ae8b _ZN2CA7Context18commit_transactionEPNS_11TransactionE + 317
28 QuartzCore 0x029cee03 _ZN2CA11Transaction6commitEv + 561
29 QuartzCore 0x029d0674 _ZN2CA11Transaction17flush_transactionEv + 50
30 UIKit 0x02ae28bc _UIApplicationHandleEventQueue + 8398
31 CoreFoundation 0x022306ff __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__ + 15
32 CoreFoundation 0x0222638b __CFRunLoopDoSources0 + 523
33 CoreFoundation 0x022257a8 __CFRunLoopRun + 1032
34 CoreFoundation 0x022250e6 CFRunLoopRunSpecific + 470
35 CoreFoundation 0x02224efb CFRunLoopRunInMode + 123
36 GraphicsServices 0x05cf5664 GSEventRunModal + 192
37 GraphicsServices 0x05cf54a1 GSEventRun + 104
38 UIKit 0x02ae8bfa UIApplicationMain + 160
39 Handbook 0x0007513d main + 93
40 libdyld.dylib 0x04b11a21 start + 1
41 ??? 0x00000001 0x0 + 1
)
libc++abi.dylib: terminating with uncaught exception of type NSException

This issue occurs when a storyboard refers to a deleted or renamed IBOutlet. If you navigate to the connection instepctor of the ViewController, you shoud notice an exclaimation mark as shown below.

Remove the IBOutlet mapping by clicking on X mark and remove the orphaned IBOutlet.

Filed Under: IBOutlet, ios, NSUknownKeyException

Swift – WebView demo

November 6, 2019 By Ravi Shankar 24 Comments

Updated for Swift 5

In this short tutorial, we will see an example in Swift programming language using UIWebView. The WebView will load a webpage and provide option to refresh, stop, go back and go forward. This tutorial should give a brief overview on how to declare IBAction, IBOutlets and use Objective-C classes (NSURL and NSURLRequest) in Swift


Interface Design

Step 1: Create a Single View Application Project and make sure to select the programming language as Swift

Choose Swift Language in Xcode

Step 2: Select Main.Storyboard under Project navigator. then drag and drop WebView and Toolbar from Object Library to the View Controller.

Step 3: Now place four bar button items on Toolbar to provide stop, refresh, go back and go forward functionality. You can also use flexible and fixed separators for aligning the bar button items.

Use Suggested Constraints in Xcode 6

Step 4: Make sure to use the SuggestedConstraints for WebView and Toolbar. You can do this by selecting the controls and using the Reset to Suggested Constraints available as part of Resolve Auto Layout option. This would ensure that the controls gets adjusted automatically depending upon the device’s screen width and height.

Reset to Suggested Constraints

Updated – 28/08/2014

Since some users are facing problems with Reset to Suggested Constraints in Xcode 6 Beta 6, you can use Pin option to define the four constraints using the default values. Then click Add 4 Constraints available at the bottom of the screen.

201408281213.jpg

Write Code

Step 5: Navigate to ViewController.swift file on the Project Navigator. Add the following line after class ViewController: UIViewController which defines the IBOutlet element for WebView.

@IBOutlet var webView: UIWebView!

Then connect this IBOutlet element to the WebView on InterfaceBuilder.

Step 6: In the viewDidLoad function, create URL and NSURLRequest for the webpage and associate this with the WebView by calling loadRequest method.

override func viewDidLoad() 
{   
  super.viewDidLoad() 
  let url = NSURL(string: "https://rshankar.com")
  let request = NSURLRequest(url: url! as URL)       
  webView.delegate = self
  activityIndicator.hidesWhenStopped = true
  activityIndicator.startAnimating()
  webView.loadRequest(request as URLRequest) 
}

Step 7: Now add the corresponding IBAction methods for the four buttons stop, refresh, go back and go forward. And connect these IBActions to the buttons on Interface builder.

func webViewDidFinishLoad(_ webView: UIWebView) 
{ 
      activityIndicator.stopAnimating()   
}                                                                                           

@IBAction func doRefresh(_: AnyObject) {                                                              
  webView.reload()
}

@IBAction func goBack(_: AnyObject) 
{   
   webView.goBack()    
}

@IBAction func goForward(_: AnyObject) {
  webView.goForward()
}

@IBAction func stop(_: AnyObject) 
{
   webView.stopLoading()
}

Step 8: Compile and run the project by selecting a suitable simulator.

Download the souce code from GitHub.

Filed Under: ios, Programming, Xcode Tagged With: Swift, WebView, Xcode

Value for SWIFT_VERSION cannot be empty.

November 6, 2019 By Ravi Shankar Leave a Comment

This error occurs when the Swift version is not specified under Swift Compiler Languge for your project.
All you need to do is, Navigate to Build Settings -> Swift Compiler Language, from the drop down specify the Swift version.

Filed Under: ios, Swift Compiler, Swift Version

Unable to simultaneously satisfy constraints.

October 17, 2019 By Ravi Shankar

A common error that we would see when using Auto Layout is

Unable to simultaneously satisfy constraints. Probably at least one of the constraints in the following list is one you don’t want.

Debugging the verbose shown in the console might look difficult but if you closely go through the messages you should be able to identify the issue.

Let us take the following Auto Layout Constraint error messages.

Try this:
(1) look at each constraint and try to figure out which you don’t expect;
(2) find the code that added the unwanted constraint or constraints and fix it.
(
“<NSLayoutConstraint:0x281862940 UIImageView:0x14ddb28e0.width == UIImageView:0x14ddb28e0.height (active)>”,
“<NSLayoutConstraint:0x28181c190 V:|-(5)-[UIImageView:0x14ddb28e0] (active, names: ‘|’:UITableViewCellContentView:0x14ddb2750 )>”,
“<NSLayoutConstraint:0x28181cc30 UIImageView:0x14ddb28e0.width == 0.22*UITableViewCellContentView:0x14ddb2750.width (active)>”,
“<NSLayoutConstraint:0x28181eb20 V:[UIImageView:0x14ddb28e0]-(5)-| (active, names: ‘|’:UITableViewCellContentView:0x14ddb2750 )>”,
“<NSLayoutConstraint:0x28186e080 ‘UIView-Encapsulated-Layout-Height’ UITableViewCellContentView:0x14ddb2750.height == 93 (active)>”,
“<NSLayoutConstraint:0x28186e030 ‘UIView-Encapsulated-Layout-Width’ UITableViewCellContentView:0x14ddb2750.width == 375 (active)>”
)

Will attempt to recover by breaking constraint
<NSLayoutConstraint:0x281862940 UIImageView:0x14ddb28e0.width == UIImageView:0x14ddb28e0.height (active)>

The constraints associated with this UIImageView are

This error is result of more than one constraints staistying the same condition. The last line in the log message tells one of the constraint causes this issues. “width == height”. Now identify the second constraint which tries to set the width of this imageview.

There are two constraints trying to set the width for the image view, 1. Propotional Width to and 2. Aspect Ratio. After removing the unwanted constraint and you are good to go.

Let us see one more example for the Constraint errors.

(
“<NSLayoutConstraint:0x281818f00 UIButton:0x14ddb7fe0’BOOK’.height == 50 (active)>”,
“<NSLayoutConstraint:0x28186c050 V:|-(20)-[UIButton:0x14ddb7fe0’BOOK’] (active, names: ‘|’:UITableViewCellContentView:0x14ddb7e50 )>”,
“<NSLayoutConstraint:0x28186c0a0 V:[UIButton:0x14ddb7fe0’BOOK’]-(20)-| (active, names: ‘|’:UITableViewCellContentView:0x14ddb7e50 )>”,
“<NSLayoutConstraint:0x28181aad0 ‘UIView-Encapsulated-Layout-Height’ UITableViewCellContentView:0x14ddb7e50.height == 90.5 (active)>”
)

Will attempt to recover by breaking constraint
<NSLayoutConstraint:0x281818f00 UIButton:0x14ddb7fe0’BOOK’.height == 50 (active)>

 

The last line gives a clue, there are two constraints trying to set the height of the BOOK button. And if you look at the constraints for the BOOK button, you can find 1. Top and Bottom constraints are set for button and 2. Height constraint is also set for Button. Removing any one of the constraint would fix the problem.

Filed Under: Auto Layout, Constraints, NSLayoutConstraint

Upload missing dSYMs to see crashes

October 17, 2019 By Ravi Shankar

If you are using Firebase Crasyltics in your app and if bitcode is enabled then you might see this message

“Upload missing dSYMs to see crashes” when trying to view reported crash in Firebase Crashlytics. In order the see the stack trace, you can try uploading the dSYMs files.

Compress .dSYMs files.

  • Launch Xcode Organizer.
  • Select the App on the Right hand side and navigate to the version which caused the crash.
  • Right click on the version and select Show in Finder.

  • Right click the archive file and select “Show Package Contents”.
  • Copy all the dSYMs files to another location.
  • Compress the file and upload the compress file to Firebase to see see the stack trace.

iTunesConnect

Another option is to download dSYMs files from iTunes Connect.

Select your App -> Activity section -> Select the Version -> Download dSYSM

Filed Under: Crash, dSYMs, Firebase

  1. Pages:
  2. «
  3. 1
  4. 2
  5. 3
  6. 4
  7. 5
  8. 6
  9. 7
  10. ...
  11. 44
  12. »
« Previous Page
Next Page »

Primary Sidebar

TwitterLinkedin

Recent Posts

  • How to set preferred Wifi network on Mac
  • Attribute Unavailable: Estimated section warning before iOS 11.0
  • How to recover Firefox password from Time Machine backup
  • Show hidden files and folders on Mac
  • How to request a refund for apps in App Store

Pages

  • About
  • Privacy Policy
  • Terms and Conditions

Copyright 2022 © rshankar.com

Terms and Conditions - Privacy Policy