iOS and Android - Bridge Two

June 1, 2019
ios android comparison

Icons

iOS offers an intuitive interface to group assets of different density together. All I have to do is switch to “Assets.xcassets” folder, create a new image set and drop the images for different screen resolutions. While in Android, I need to put the assets of varying density into corresponding folders kept for them like “mdpi”, “hdpi”, “xhdpi”, “xxhdpi” and “xxxhdpi”. Can’t say which one is better but both offer excellent IDE support.

Android iOS
GeoQuiz/app/src/main/res/drawable-hdpi/arrow_left.png GeoQuiz/Assets.xcassets/arrow_left.imageset/arrow_left.png
GeoQuiz/app/src/main/res/drawable-xhdpi/arrow_left.png GeoQuiz/Assets.xcassets/arrow_left.imageset/arrow_left-1.png
GeoQuiz/app/src/main/res/drawable-xxhdpi/arrow_left.png GeoQuiz/Assets.xcassets/arrow_left.imageset/arrow_left-2.png

Portrait and Landscape

Android has different layout files for different screen dimensions. In iOS, Storyboard groups all the layouts and their interconnections. Development of Layouts for different screen sizes is done using IDE assist. In iOS case, the corresponding ViewController is chosen from the Storyboard and then “Vary For Traits.” is selected where variations are given based on width and height. What it mostly does is excludes constraints not needed and adds new constraints based on screen dimension.

Android iOS
Landscape Landscape
Portrait Portrait

Referencing Views

In Android, the binding of the layout with the View Controller(Activity) is seamless with the Kotlin extensions. As soon as the activity’s content layout is set the views in the layout are accessible via their ID’s. However, in case of iOS, the views that need to be referenced in the code have to be tagged with @IBOutlet and needs to be wired in the Storyboard by pressing control and dragging from the ViewController to the actual view that needs to be referenced.

Android - Binding View [QuizActivity.kt]

class QuizActivity : AppCompatActivity() {
    //..
    override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            setContentView(R.layout.activity_quiz)
            //..
    }

    private fun updateQuestion() {
        //..
        val question = questionBank[currentIndex].questionKey
        question_text_view.setText(question)
        //..
    }
    //..
}

iOS - Binding View [QuizViewController.swift]

class QuizViewController: UIViewController {
    //..
    @IBOutlet var questionLabel: UILabel!
    //..
    private func updateQuestion() {
        //..
        let question = getString(key: questionBank[currentIndex].questionKey)
        questionLabel.text = question
        //..
    }    
}

Passing data

Forward

In Android, data is passed between the activity via intents. At a fundamental level, it works via binder an IPC mechanism to share data between the Process. I believe it is smart enough to figure out if the data is to be passed within the same Process or outside of the operating Process and optimize the way data is transferred without incurring overhead costs. In iOS, I have to use Segue which gives me a reference to the Viewcontroller, which I use to directly pass the required data that keeps me wondering how the data will be passed if the controller belongs to a different process(I’ll figure this out later).

Android - Passing data Forward [QuizActivity.kt,CheatActivity.kt ]

class QuizActivity : AppCompatActivity() {
    //..
    override fun onCreate(savedInstanceState: Bundle?) {
        //..
        cheat_button.setOnClickListener {
            val answerIsTrue = questionBank[currentIndex].isAnswerTrue
            val intent = CheatActivity.getIntent(this, answerIsTrue)
            startActivityForResult(intent, REQUEST_CODE_CHEAT)
        }
        //..
    }
    //..
}


class CheatActivity : AppCompatActivity() {
    //..
    companion object {
        const val EXTRA_ANSWER = “answer”
        //..
        @JvmStatic
        fun getIntent(context: Context, answer: Boolean): Intent {
            val intent = Intent(context, CheatActivity::class.java)
            intent.putExtra(EXTRA_ANSWER, answer)
            return intent
        }
        //..
    }
    //..
}

iOS - Passing data Forward [QuizViewController.swift,Main.storyboard,CheatViewController.swift ]

class QuizViewController: UIViewController {
    override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
        let cheatViewController = segue.destination as? CheatViewController
        cheatViewController?.questionWithAnswer = questionBank[currentIndex]
    }
}


class CheatViewController: UIViewController {
    var questionWithAnswer: Question?
    //..
}


<viewController id="GDd-qq-Id5" customClass="CheatViewController"/>
<button id="zAu-D1-ib4">
    <state key="normal" title="CHEAT!"/>
    <connections>
        <segue destination="GDd-qq-Id5" kind="show" id="DVN-ew-vqr"/>
    </connections>
</button>

Backward

In Android passing data back is again via intents which are set in setResult in the source Activity from which this data has to be transferred back. While in iOS it is via Segue through unwinding.

Android - Passing data Backward [QuizActivity.kt,CheatActivity.kt ]

class QuizActivity : AppCompatActivity() {
    //..
    override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
        //..
        if (requestCode == REQUEST_CODE_CHEAT) {
            //..
            isCheated = CheatActivity.wasAnswerShown(data)
        }
    }
}


class CheatActivity : AppCompatActivity() {
    //..
    companion object {
        const val EXTRA_CHEATED = “cheated”
        //..
        @JvmStatic
        fun wasAnswerShown(result: Intent): Boolean {
            return result.getBooleanExtra(EXTRA_CHEATED, false)
        }
        //..
    }
    //..
    private fun setAnswerShownResult() {
        val data = Intent().apply {
            putExtra(EXTRA_CHEATED, true)
        }
        setResult(Activity.RESULT_OK, data)
    }
}

iOS - Passing data Backward [QuizViewController.swift,Main.storyboard,CheatViewController.swift ]

class QuizViewController: UIViewController {
    @IBAction func didCheat(sender: UIStoryboardSegue) {
        let cheatViewController = sender.source as? CheatViewController
        isCheater = cheatViewController?.cheater ?? false
    }
}


class CheatViewController: UIViewController {
    var cheater:Bool = false
    //..
    @IBAction func showAnswer() {
        //..
        cheater = true
    }
}


<navigationItem key=“navigationItem” title=“Cheat” id=“djQ-lA-u9P”>
    <connections>
        <segue destination=“2MX-mY-Am0” kind=“unwind” id=“aCg-u3-G7N”/>
    </connections>
</navigationItem>
<exit id=“2MX-mY-Am0” userLabel=“Exit” sceneMemberID=“exit”/>

Segue

In iOS, the mappings between the ViewController have to be wired using the StoryBoard. First NavigationController is created, then all the View controllers are embedded within the NavigationController by selecting “Editor > Embed In > Navigation Controller.”. The forward navigation between the view controller is defined by pressing control and dragging from the UI to the ViewController that needs to be shown. The backward navigation is defined by selecting the correct UI element and mapping it to Exit, and this would allow you to map to a corresponding action in the destination view controller.

Save and Restore

In Android, it is easier to lose the controllers(Activity) state unless special care is taken to save the data, on the other hand, something as small as rotation doesn’t make an iOS controller to lose its state. I am not for one to comment if it was poor design on the Android front that makes the developer pay the price. I think Android is making a painful recovery on this front.

Android [QuizActivity.kt]

class QuizActivity : AppCompatActivity() {
    //..
    override fun onCreate(savedInstanceState: Bundle?) {
        //..
        if (savedInstanceState != null) {
            currentIndex = savedInstanceState.getInt(KEY_INDEX, 0)
            answers = savedInstanceState.getSerializable(KEY_ANSWERS) as HashMap
        }
        //..
    }
    //..
    override fun onSaveInstanceState(savedInstanceState: Bundle) {
        super.onSaveInstanceState(savedInstanceState)
        savedInstanceState.putInt(KEY_INDEX, currentIndex)
        savedInstanceState.putSerializable(KEY_ANSWERS, answers)
    }
    //..
}

iOS [QuizViewController.swift]

class QuizViewController: UIViewController {
    
}

comments powered by Disqus