iOS Archive - CraftCoders.app https://craftcoders.app/category/ios/ Jira and Confluence apps Wed, 14 Aug 2024 12:27:54 +0000 en-US hourly 1 https://wordpress.org/?v=6.5.3 https://craftcoders.app/wp-content/uploads/2020/02/cropped-craftcoders-blue-logo-1-32x32.png iOS Archive - CraftCoders.app https://craftcoders.app/category/ios/ 32 32 Smartphone Sensors for Dummies https://craftcoders.app/smartphone-sensors-for-dummies/ Sun, 09 Sep 2018 17:13:29 +0000 https://craftcoders.app/?p=616 Read More]]> On my way exploring Augmented Reality under iOS, I have come across Core Motion Framework, which provides tools for working with motion- and environment-related data your device captures. There are numerous applications for this kind of information, especially if we are talking about location-based services or any apps which need to be sensitive to their environment. Since, as a mobile developer, you do not necessarily possess a degree in Physics, and math skills have gathered a thick layer of dust by now, let’s try to make complicated things more approachable. In today’s blog, we are going to talk about the built-in motion sensors and the way you can use them for the good of humanity. Or to build a silly useless app based on a cheesy cartoon, let’s see which one it will be.

All kinds of -meters and a -scope.

In this section, we are going to be looking into 5 different kinds of sensors, built into your device and reading motion information while you’re not watching. For one, it’s good to know all the opportunities you have regarding tracking environment-related events, and, secondly, what an excellent ice-breaker for the next social event you’re attending! So let’s dive right in.

Accelerometer

This guy is not a slyish type of sensor, hiding its purpose behind some fancy mixture of greek and latin. It is a simple kind of guy, clear about its intentions: just minding its own business, measuring your acceleration here and there. If you know what acceleration is, you basically understood what this sensor is all about. If you don’t, that is weird, and I don’t think I can help you, so just stop reading and go rethink your life choices.

No, I’m kidding, I can totally help you. Just give me a call, we can talk this out. For the rest of you, we are just going to make one step further towards the understanding of how this sensor works. For this, imagine yourself on a freefall tower ride in an amusement park. The seatbelts are fastened, and you are going up. Since our tower is pretty tall, and the guy in control is really looking forward to his lunch break, you are going up pretty fast, so that you start feeling your body pressing harder on the seat beneath you. Seconds later, you are at the top and getting ready to experience the free fall. Our hungry amusement park employee presses the big red button on the panel in front of him, and you start falling all the way down, floating just a little above your seat. This time, you feel the shoulder harness pressing into your skin, holding you back from lifting up too much. This is what an accelerometer experiences all the time. Well, maybe it’s not that exciting, we’ll never know. But the principle used in the sensor is the same. A body, loosely attached to a moving plate, is going to experience forces, pushing it in the direction, opposite to the movement. By measuring the extent, to which these forces cause the body to move, our sensor is able to tell us, what the acceleration of the plate is. If you are interested in how it looks in real life, you can check this link or this one.

Pedometer

Next up is the pedometer, the favorite sensor of all the fitness-junkies. This is the guy who counts the number of steps between your bed and your fridge (because little achievements matter) and celebrates how sporty you are when you take 10.000 steps inside a shopping mall. How does he do that? The answer is a deep understanding of how walking works. Each step we take consists of several phases, in which we tilt and accelerate in different directions and to a different extent. Distinguishing between sets of movements that constitute a single step allows this sensor to count their total amount. Earlier in the days, separate mechanical sensors have been used to recognize the step pattern. Pedometers inside modern devices usually rely heavily on input data provided by other inertial sensors. They do not measure the motion themselves, and only make sense of the given measurements. This makes our pedometer a software wannabe among real hardcore hardware sensors we are discussing here. But it allowed you to stare at your monitor for the rest of the day since you’ve reached your walking goal, so be kind to it.

Magnetometer

I think we can all imagine, what this one is trying to measure. What is more interesting, is how it goes about the task. To answer this question, we would usually have to talk about electrons, conductors, circuits and voltage and all that jazz. But since I promised to make things simple, let’s take an example of something that is more fun. Like tourists. Tourists are exceptional beings, who manage to get fascinated by a large number of things in a highly limited time. So imagine a place of interest, with an entrance, an exit and a path in between. Let’s say it’s a sea life museum, with all kinds of fish swimming around our tourists in fish tanks, which build an arch around the path. Our tourists would form a large group at the entrance and, the moment the museum opens its doors, a flow of fascinated humans is going to flood all the way up to the exit. They would keep walking, building what looks like a steadily moving queue through the whole museum. This is how electrons are usually portrayed in a circuit, moving along from where there is a whole bunch of them to where there is none.

Usually, the tourists are very busy, keeping their fascination ratio high. This accounts for a steady flow throughout the whole museum. But some things are especially magnetic to them since they would make for a good background on a photo. As a museum manager, we would like to measure, which particular spots are especially magnetic for the tourist (you see where I am going with this?). To do so, we come up with a brilliant idea – a magnetometer. We know, that if some especially magnificent fish is going to swim by on one side of the arched fish tank, the tourists are going to want to make a picture. Instead of building a steady flow in the middle of our path, they are going to get attracted to one side of it, to get their photo and only then pass by. People are heavy, so there is going to be a weight difference between the two sides of the path, which we could measure and translate into the magnetic power of a spot. The stronger the attraction – the more weight difference we will be registering. That’s like tourist attraction management 101. But other than learning how to pursue an alternative career in tourism, you have just figured out the way magnetometers work. Electrons with tiny cameras are getting attracted by magnetic fields, and gather closer to the source of attraction, on one side of the conductor (our path segment). This causes a measurable weight difference between the left and right parts of the conductor (voltage). The whole thing is called the Hall effect, so now you can also google it, and I can move on to the next sensor.

Barometer

Barometers are some commonly used sensors, which you might have come across in real life. Especially if you hang out on ships a lot. Or around people who… like weather, I guess? The purpose of a barometer is to measure atmospheric pressure. They can take many forms, one of the simplest being that of two conjoined glass containers with water inside, only one of which is sealed. The other one is a thin spout which rises above the water level. The atmospheric pressure is measured based on the level of water in the spout.

Now that I’m done paraphrasing the Wikipedia page on barometers we can move to the way the sensor works inside your phone. Instead of containers with water or, god forbid, mercury, a membrane sensitive to pressure is used. The extent, to which it gets distorted is measured to calculate the atmospheric pressure, which causes the deformation. That’s it, I guess barometers are only fascinating to weather people.

Gyroscope

Last but not least is the gyroscope. This is a really fancy one, just look at it. It looks like it belongs on an oak table in the office of some big corporate boss from the early 2000’s. It can do some pretty impressive tricks as well, just check this video out. Instead, it is consigned to oblivion behind the cover of your phone.

Of course, the gyroscope inside your device doesn’t have all these rings orbiting around it in different directions. Instead, it looks a lot like an accelerometer, if you still remember what that was. Only this time, the body is continually moving back and forth (oscillating, if you are in a search for the fancy word of the week). The moment the device is rotated on an axis perpendicular to the plate it is fixated on, it is going to move along the third axis. Because physics. The movement is measured and can be used to calculate the device orientation. To have a picture of the sensor in your head, watch this video.

Up! we go

If you want to get to know Core Motion, learning by doing is the way to go. That is why today we are going to be building a cheesy little app, which uses the accelerometer inside our iPhone to distinguish top from bottom. If this is your first iOS app and you need some help getting started, you should probably make your way through this tutorial first. But if you are as far in your iOS developer career, as being comfortable with creating a new single-screen project in Xcode, you are all set for what’s coming up next.

Preparing the UIView

In our app, we want to be able to point upwards in the direction of the sky however the device is rotated. To do so, we need an indicator of some kind. I am using an image, so the first thing I am going to be setting up in my ViewController is a UIImageView object. I do want it to fill my whole screen, so the width and height of the frame are going to correspond to the dimensions of the device screen, and the image itself is going to be placed into that frame with the .scaleAspectFit option. To make the image show, I am going to add it as a subview of the current controller’s view. If we ran our app at this point, we would see a static full-screen image of whatever we’ve chosen to indicate the direction.

class ViewController: UIViewController {
    private var upView: UIImageView!    

    func showUp() {
        let screenSize: CGRect = UIScreen.main.bounds

        upView = UIImageView(frame: CGRect(x: 0, y: 0, width: screenSize.width, height: screenSize.height))
        upView.image = #imageLiteral(resourceName: "bloons")
        upView.contentMode = .scaleAspectFit
        
        self.view.addSubview(upView)
    }
}

Getting CoreMotion updates

The communication with the CoreMotion framework is handled by the CMMotionManager. After creating an instance of this class, we can ask it for the motion updates and even set up the intervals we want to receive the updates in. To get the updates, we need to give our motion manager an OperationQueue to send its data to. This needs to be done in case we are going to be flooded with motion information, so much so, that our device stops handling the events occurring in the UI. To prevent this from happening, we could make the motion manager send all the updates to another thread. This way, our app would stay responsive for the user, even though it is receiving a large number of updates in the background. In the simplified example below I am using one and the same Queue for both, motion information and the UI work.

import CoreMotion
class ViewController: UIViewController {
    private var motionManager: CMMotionManager!
    private var upView: UIImageView!

    func setupCoreMotion() {
        motionManager = CMMotionManager()
        motionManager.deviceMotionUpdateInterval = 0.05
        startAsyncDeviceMotionUpdates()
    }

    fileprivate func startAsyncDeviceMotionUpdates() {
        motionManager.startDeviceMotionUpdates(to: OperationQueue.current!, withHandler: {
            (deviceMotion, error) -> Void in
            if(error == nil) {
                self.handleDeviceMotionUpdates(deviceMotion)
            } else {
                // handle error
            }
        })
    }
}

The second parameter our motion manager needs is a method, which will be invoked every time new motion information comes in. At this point, we can also take care of possible errors, which could occur while retrieving data from the sensors. Handling the motion updates is going to be our next task.

Handling the updates

All the motion data we can retrieve is held by a CMDeviceMotion object we receive in each update. All we need to do is figure out the rotation axis we want to be calculating (since there are 3 different directions you can rotate your iPhone in), apply the correct formula and transform our image. Let’s take a look at the axis first.

The picture above can be found in Apple documentation and shows how the rotation types are going to be referred to. In this tutorial, we will only cover the rotation on the Z-axis (yaw). This is going to take care of pointing to the sky as long as we are holding our device perpendicular to the ground. You can find the detailed mathematical explanation of the atan2 formula we are applying to calculate yaw, as well as its equivalents for roll and pitch, here.

import CoreMotion
class ViewController: UIViewController {
    private var motionManager: CMMotionManager!
    private var upView: UIImageView!

    fileprivate func handleDeviceMotionUpdates(_ deviceMotion: CMDeviceMotion?) {
        if let gravity = deviceMotion?.gravity {
            let rotationDegrees = atan2(gravity.x, gravity.y) - Double.pi
            rotate(with: rotationDegrees)
        }
    }
    func rotate(with degrees: Double) {
        upView.transform = CGAffineTransform(rotationAngle: CGFloat(degrees))
    }
}

The very last step towards building our mini-app is applying the calculated rotation degrees to the image we have added to our screen in the first step. To do so, I am using the CGAffineTransformation, previously converting a double value into a CGFloat which is going to be passed as an argument, while initializing the transformation. Don’t forget to wire both, the image creation and the motion manager set up in the viewDidLoad method. This way, all the elements are going to be initialized right after your View has loaded.

class ViewController: UIViewController {
    private var motionManager: CMMotionManager!
    private var upView: UIImageView!

    override func viewDidLoad() {
        super.viewDidLoad()
        showUp()
        setupCoreMotion()
    }
}

That’s it. Build, run and see the result for yourself! Here is what I’ve got:

I hope you’ve enjoyed our little journey into the functionality of CoreMotion and all kinds of sensors your device is stuffed with. Experiment with it for yourself and let’s learn from each other.

Dannynator.

]]>
My date with Dart and Flutter https://craftcoders.app/my-date-with-dart-and-flutter/ https://craftcoders.app/my-date-with-dart-and-flutter/#respond Mon, 09 Jul 2018 13:54:34 +0000 https://billigeplaetze.com/?p=334 Read More]]> Motivation

…Hey, guys! It’s time for another blog post from me. This time I want to write about my experiences with Flutter and Dart. Every software developer is regularly confronted with new concepts, frameworks or languages, so am I. I learned to program with Android 2.0. Yes, I’m getting old… Meanwhile, I am not so active in the Android world anymore. But since we code Android apps on hackathons every now and then, I don’t get out of practice. Nevertheless, I am a little spoiled by the development of UWP apps. Also, the time on hackathons is very limited, which is why one does not like to deal with boilerplate code. Let’s be honest… Android (Java) is full of it! Therefore, alternatives are needed. Okay, so it would be obvious to try Xamarin now, but where would be the fun? I have no plans this weekend and Google seems to have created a real alternative with Flutter and Dart! Sounds like a hot date ? I’ve never worked with Dart or Flutter before, but at some point, you have to start. So this post will be about my experiences and not a tutorial.

So what are the advantages of Flutter?

Google promises not only that you can develop apps very fast, but also that they run on iOS like Android. In addition, Dart and Flutter are focused on developing reactive apps. I’ve never written apps in a reactive manner before, but at least the theoretical foundations were covered at our university. *Yes, Dannymausi I’m talking about the hipster teacher 😉 *

And what do I want to program?

I’ve been thinking for a long time what I can achieve in less than two days (hitting the gym and watching Netflix is still important!). I don’t know the language or the framework…. But yolo , after all, Google promises that you are significantly faster in developing. So here’s the idea: We’ve finished the first rotation of our blog posts. We came up with the idea that once every member of us has written a blog post we select the best and the winner gets a little something. Basically, this means we have to make a voting every 5 to 6 weeks. There is one catch: Since Danny is from Russia we have to very be very cautious about our voting system. We had so many different voting systems… I can’t even count them! We even voted about how to vote… Time to time we vote with two or three voting shares. Sometimes we have several voting rounds. Since we always discuss and try to optimize our voting systems, it’s time for an App. The app should allow you to vote for one of the five possible authors (including yourself). After you have voted, the application navigates to a second page where you can see the election results. You can not vote again. However, a new election can be started, this must be propagated to all clients.

Getting Started

So now I have two days to try out a new language and a new framework….. What the hell am I doing to myself ?? So that it won’t be too unpleasant, I decided to chill on YouTube first. Fortunately, the Google IO 18 wasn’t so long ago and there are interesting talks about Flutter. Here are the ones I looked at:

  1. Build reactive mobile apps with Flutter
  2. Total mobile development made fun with Flutter and Firebase

So what should you take out of these videos? On the one hand Flutter is about widgets – everything is a widget. A screen is a widget tree. One distinguishes between Stateless and Statefull widgets. But we will use the BLOC pattern from “Build reactive mobile apps with Flutter” for our app.
On the other hand, Firebase seems to offer itself perfectly as a backend. As this blog post is not a tutorial, I recommend to watch the videos. Our task is to create a Firebase database. And to create a client according to the BLOC pattern.

Implementation

The Backend alias Firebase

So this is the easy Part. Since I have already written such a long motivation, how about a gif?

How to create the Backend 😉

The Voting Screen

To keep the code manageable I decided to write all widgets in the main.dart file. In a real project you would surely divide them into different files but for experimenting, I think it’s better to keep everything at one place. Since time is limited, I thought we’d keep the layout simple. Here is my design:

Lets Check:

  • We have a ListView
  • We have an AppBar with Title

That’s what I call minimalistic 🙂
After I haven’t committed to any tutorial here, I don’t want to go into the exact structure of the code. Much more interesting is how we build our UI based on the authors in Firebase. First, we have to connect Firebase to our app. Just follow the instructions on Firebase or watch this video.

From Firebase to Voting

But what is the right place to read from the database? If we follow Google’s BLOC patterns, we need a stream. Using this stream, information from our database will flow straight into our UI. So we don’t have to hold a state. But what if we need information from the database? So not the information from the database flows into the UI but rather the UI pulls information from the database? To solve this elegantly, we would need a short-term memory. Therefore we will let the stream from the Firebase database flow into a so-called BehaviorSubject. This BehaviorSubject can always return the last element seen on request. So it holds our state.

Finally, we need a so-called StreamBuilder in our view. The StreamBuilder builds itself on the latest snapshot interaction with the specified stream an whose build strategy is given by a builder. So lets look at the code:

    class Bloc {
            final BehaviorSubject<QuerySnapshot> _firebaseSubject =
                    BehaviorSubject<QuerySnapshot>(seedValue: null);

            Bloc() {
                _firebaseSubject.addStream(Firestore.instance.collection("AuthorVotes").snapshots());
            }

            Stream<QuerySnapshot> get firebaseStream => _firebaseStream.stream;
    }

Here we connect the BlocClass with the Firebase Database alias Firestore

    class AuthorGrid extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final bloc = BlocProvider.of(context);
    return new StreamBuilder(
      stream: bloc.firebaseStream,
      builder: (context, snapshot) {
        if (snapshot != null && !snapshot.hasData) return const Text('Loading...');
        return ListView.builder(
          itemCount: snapshot.data.documents.length,
          itemBuilder: (context, index) => 
            _buildItem(context, snapshot.data.documents[index]), 
        );
      },
    );
  }

Here we build a widget based on the stream comming from our blocclass
But wait?! Where comes the BlocClass from? You’re right, the BlocClass itself is also a kind of state we need to have manage. Although the UI of this app is manageable and I could have passed the bloc object through the tree as a parameter, there are better alternatives. With the help of an inheriting widget we are able to reach the object from the entire subtree.

From Voting to Firebase

But How do we get updates to the Firebase? Actually, it’s pretty easy. We just have to let a stream flow in the opposite direction. For this purpose we create a stream controller in our BlocClass. The StreamController controls our stream. It allows to feed events into the stream via a sink as well as to consume the stream by calling the stream methode. All we have to do is: (1) A Click on the list submits an event to the stream. (2) Consume this stream and start a Firebase transaction at the appearance of a event.

class Bloc {
  final StreamController<Vote> _voteController = StreamController<Vote>();

  Bloc() {
    _voteController.stream.listen((data) => commitToFireBase(data));
  }

  Sink<Vote> get voteSink => _voteController.sink;

  void commitToFireBase(Vote vote) {
    Firestore.instance.runTransaction((transaction) async {
      DocumentSnapshot freshSnap = await transaction.get(vote.votedAuthor.reference);
      await transaction.update(freshSnap.reference, {'Votes': freshSnap['Votes'] + 1});
    });
  }
}

This is all we need in our BlocClass. As you can see the voteSink is the place to commit new Events to our Stream. In the Constructor of this Class, we say that the stream is consumed by a function, which is also defined within the Blocclass. In this function, we start a transaction to safely increment the votes for the author by one. Notice how the actual Author is provided by the vote event itself.

Widget _buildItem(
    BuildContext context, DocumentSnapshot document, AuthorBloc authorBloc) {
  return new AuthorSquare(
    author: document['Author'],
    imageUrl: document['Image'],
    onTab: () {
      bloc.voteSink.add(Vote(document));
    },
  );
}

As you can see we just add a new vote event. There is no logic in the UI – all is done by in the BlocClass.

Navigation

I know the blog post is getting long and I said it won’t be a tutorial. But there’s another detail I want to work out. This has given me the most stuggle and I’m sure there are more elegant ways to solve it. Nevertheless, I would like to share and discuss my solution with you. The challenge I encountered was navigation. Yeah, Flutter’s got a pretty well-designed system for navigation. But what I want is more than the standard requirements. So here a some requirements which I had set for myself:

  1. As a voter, after making my choice, I don’t want to be able to re-vote.
  2. As a voter, when I open the application I want to see the election results directly, if I have already voted.
  3. As a voter, I want to be brought directly to the polls from the election results if there is a new election.
  4. As a noob in reactive programming, I don’t want the navigation logic to be located in the view.

Well, as you can see, I didn’t make my life easy. One difficulty was to get the BuilderContext from the widget into the blocClass. However, you need the BuilderContext to create a new route. In Flutter Navigation is based on so called “Routes”. A Route is a mapping from a name to a widget. The Navigator managed this routes by adding or removing them from the navigation stack. So first we needed to get the BuildContext to the blocClass. For this we can use a BehavoirSubject again. When we create a widget, we pass the BuildContext as an event. Since a BehavoirSubject always remembers the last element seen, we are always able to navigate. Furthermore we can pass the widget we wish to navigate to into another stream. If we combine both streams, we have everything we need to navigate.

It is important that we do not use BehaviorSubject for the widget stream. We can’t remember the last widget we saw. Otherwise we would end up in an endless navigation. Therefore we use a StreamController for the wigetstream. The BuildContextStream and the WidgetStream are fed into a zipper. The zipper then packs the elements from the two streams into a tuple and the cool thing is: Since we use a BehaviorSubject for the BuildContext, we always get a context here. But since the widget comes via aStreamController, a tuple is only generated if we actually have passed a widget somewhere in our code into the stream.

class Bloc {
  final StreamController<Widget> _widgetController = StreamController<Widget>();

  final BehaviorSubject<BuildContext> _contextStream =
      BehaviorSubject<BuildContext>(seedValue: null);

  AuthorBloc() {
    Observable
        .zip2(_widgetController.stream, _contextStream.stream,
            (a, b) => new Tuple2<Widget, BuildContext>(a, b))
        .listen((data) => _navigate(data));
  }

  Sink<BuildContext> get contextSink => _contextStream.sink;
  Sink<Widget> get wigetSink => _widgetController.sink;

  void _navigate(Tuple2<Widget, BuildContext> tuple) {
    Navigator.pushReplacement(tuple.item2,
        new MaterialPageRoute(builder: (BuildContext context) => tuple.item1));
  }
}

If you look at the picture from above, the code is quite straightforward. If you want to navigate simply pass the widget to navigate to. For example, when we realize that the voting results have been thrown away:

void _checkForEmptyVotes(QuerySnapshot data) {
  int votes =
      data.documents.map((snap) => snap.data['Votes']).fold(0, (a, b) => a + b);

  if (votes <= 0) wigetSink.add(new MyHomePage());
}

You’re wondering where this code is coming from again? Easy, actually. Since we already have a stream from the database, we just have to let it flow into this function. With each database update, the system checks whether navigation is required.
The other requirements are now also easy to implement. We simply write in the local settings whether we have already selected. If so, we pass the corresponding widget the stream and already we navigate.

The Result

Time for a gif!

  1. You can only vote once ?
  2. If someone votes, all devices are getting the update ?
  3. When a new election is started, all devices navigate to the election.?

Reflection

That’s been a great two days! Even though I had to fight with a lot of initial pain, I am glad to have chosen this topic. By initial pain, I mean trivial things like spending a lot of time for googling the basics of Dart, being depressed from simple compile errors or even setting up Flutter and Dart on my Computer. Despite still pending refactoring I am also relatively satisfied with the code. Even if I will hate myself for this code in a month 🙂 The concept behind Flutter is simply great so far! Personally, I find the development experience much more satisfying than vanilla Android with Java/Kotlin. I also liked Dart as a programming language very much. I liked the tooling around Flutter, too. By the way, I used Visual Studio code.
Well I will surely change some code and build some model classes. Also I didn’t care about the design yet. But there seems to be still a lot to discover here. After all, Flutter is there to build beautiful apps! I am not yet satisfied with the navigation either. For example, I don’t like my solution, which is waiting for an update of Firebase (also for the device that requested a new voting). But you will certainly read more of me and Flutter ❤

Feel free to check out the GitHub repo

]]>
https://craftcoders.app/my-date-with-dart-and-flutter/feed/ 0
Xamarin: Tabbed Page Navigation https://craftcoders.app/xamarin-tabbed-page-navigation/ https://craftcoders.app/xamarin-tabbed-page-navigation/#respond Mon, 25 Jun 2018 08:00:56 +0000 https://billigeplaetze.com/?p=92 Read More]]> If you have a background in native mobile development and for some reason had to switch to Xamarin, you are probably familiar with that “oh yeah, it doesn’t work this way here” feeling. Having to find new solutions for problems you haven’t even considered to be problems before is what I would call business as usual in Xamarin. Call me a weirdo, but that is also what made me enjoy learning the technology so much.

The project I had to dive into cross-platform development with, was a big adventure on its own – a very ambitious idea, a rather vague picture of the expected result (and therefore constantly changing requirements), a critical lack of time and the total of 3 developers, all rather new to the technology. Time pressure, general cluelessness about Xamarin and no expert looking over our shoulder turned into hours of online research, in a desperate hope that someone has already encountered the exact same issue. I know that, as any other developer, you have been there yourself, and I don’t have to tell you about that inexplicable relief of finding what you were looking for. Just as much as I don’t have to elaborate on the frustration of trying to fix the problem that you’ve created, trying to fix the problem that you’ve created, trying to fix the problem…

One of the issues, which has nearly caused such infinite loop of research, was connected to building up a pretty common navigation concept. Here is what we were trying to achieve:

After logging in, a user should see their dashboard. From there they can navigate by switching between individual tabs. Those are always present on the bottom of the screen, “framing” any active content. Each tab has it’s own navigation tree, meaning one can go back and forth between pages inside any tab. If a user decides to switch between tabs, the app remembers current position in all the trees. This way one can always pick up from where they left the tab, no matter what has happened in the meantime.

This navigation concept has been around for a while, and you could probably name a couple of apps using it off the top of your head. However, just like I have mentioned earlier, things in Xamarin are not necessarily what, where and how you are used to. It’s not rocket science, but if you are new to the platform, this little tab page navigation tutorial could hopefully save you some headache. Let’s dive right into it.

Xamarin Navigation Basics

1. Defining MainPage
The root page of your application is defined in App.xaml.cs, which is a part of the shared code. Here you can tell your app, which page to display on start.

public App()
{
    InitializeComponent();
    MainPage = new MyMainPage();
}   

2. Hierarchical navigation
Hierarchical navigation is the most basic way to move between pages in Xamarin. You can think of it as a stack of cards in solitaire. The first card in the stack is your root page. You can put new pages (or cards) on top of it in any order you like, as long as you comply with a couple of predefined rules. In a solitaire, these rules would tell you, say, to only put lower ranked cards on top of higher ranked ones. In a Xamarin app, the rules define legitimate navigation paths. They are nested in the code and influence the way a user can interact with your app.

So how does it look from a less abstract, more code-oriented perspective? Your navigation tree is represented by a NavigationPage. You can pass the root page of the tree as a constructor argument, like this:

var myNavigationPage = new NavigationPage(new MyPage());

Now that you have your base card on the table, you can add some more on top of it, by performing a push operation. In this example, you push a new page on the navigation stack after a button is clicked:

private async void Handle_MyButtonClicked(object sender, EventArgs e)
{
    await Navigation.PushAsync(new MyOtherPage());
}

If you want the top page of the stack to disappear and show the page below, do so by calling the pop method of Navigation property:

private async void Handle_DismissButtonClicked(object sender, EventArgs e)
{
    await Navigation.PopAsync();
}

Now that you can manipulate your stack, it’s time to check out, how tabbed pages work in Xamarin.

Tabbed page navigation

Step 1. Creating a Tabbed Page

public partial class MyTabbedPage : TabbedPage
{
    public MyTabbedPage()
    {
        // Your constructor code here
    }
}

All we do here is inherit from a TabbedPage class, which is provided by Xamarin. It is important to remember, that tabs will not render the exact same way on different platforms. On iOS, they will show up at the bottom of the screen, beneath the actual page content. Android, on the contrary, displays tabs on the top of the page. There are many more differences in tab behavior across the two platforms, the most important ones covered here.

Note: For our scenario, we had to go for a unified look (icon tabs on the bottom) for both, iOS and Android. If this is what you are going for as well, check out BottomTabbedPage control by Naxam.

Step 2. Making TabbedPage to your root
All you are doing here is telling your App to display the tabbed page you’ve created in the previous step on start. Use MainPage property to do so.

public App()
{
    InitializeComponent();
    MainPage = new MyTabbedPage();
}   

Step 3. Filling the tabs
How many tabs is your app going to have and what are they going to be? When you’ve decided on the navigation concept and created all the necessary pages, all you need to do is pass them to the tabbed page.

Let’s go back to our solitaire metaphor for this one. Imagine, that your game has a row of three cards in the beginning. Maybe, two of those are the basis for stacks a player needs to build, and the third one is just a single card, pointing at a trump. How do you create an analogy of this constellation within a tabbed page?

The game field you lay out your cards on is your tabbed page. It should display three cards in one row so you will need the total of three tabs. One of them is very simple since it only has one card in it. Nothing goes on top of that card, and the player does not have the power to alter it. For our app, it means that no navigation takes place inside the tab. This behavior is going to be represented by a ContentPage.

The other two cards have a more complex role. They will become stack bases. Cards are going to be put on top of them, and, maybe, taken back off. For our pages, this means that they are going to become roots of their own navigation trees, which users will interact with. By pressing buttons or swiping, they will travel from one page to another, pushing them on or popping them off the stack. To enable this, we will need a NavigationPage class to wrap around our root page. It will allow us to use the push and pop methods and let our users travel up and down the stack.

An important notice here: you should not wrap TabbedPage itself into a NavigationPage. iOS does not support this, and you are going to run into troubles trying to do so. Your concept should base on populating a TabbedPage with ContentPages and NavigationPages only.

Finally, you can add content and navigation pages to the TabbedPage using its Children property:

public Tabbs()
{
    // Your constructor code here
    Children.Add(new NavigationPage(new MyPage());
    Children.Add(new NavigationPage(new MyOtherPage());
    Children.Add(new MyContentPage());
}

Step 4. Manipulating the stacks
NavigationPages give you a couple of methods to work with the stack. We are only going to look closely at the two most basic ones today: PushAsync() and PopAsync(). These methods can be called on the Navigation property of a Page and allow you to put a new card on top of your stack or remove the top card.

Any class, which derives from Page provides you with a Navigation property. Sometimes, however, it might be useful to manipulate the navigation tree from outside the pages themselves. In order to expose the tree to other classes in the app, you might consider declaring a public variable in your App class:

public static INavigation Nav { get; set; }
public App()
{
    InitializeComponent();
    //...
    var myNavigationPage = new NavigationPage(new MyPage());
    Nav = myNavigationPage.Navigation;
}

After you took care of this, you can access the navigation tree from anywhere inside of your app:

private async void Handle_SomeNavigationButtonClicked(object sender, EventArgs e)
{
    await App.Nav.PushAsync(new MyOtherPage());
}

While this step can be useful regardless of the navigation concept of your application, it was absolutely crucial for our multiple-navigation-stacks solution, because we needed to know which branch of our navigation tree we are on at all times.

Step 5. Knowing where you are
How do you let the App know which tab is currently active so that the pages get pushed to the right stack? One way to achieve this would be to let TabbedPage take care of changes by overriding its OnCurrentPageChanged() method:

public partial class MyTabbedPage : TabbedPage
{
    public MyTabbedPage()
    {
        // Your constructor here 
    }

    protected override void OnCurrentPageChanged()
    {
        base.OnCurrentPageChanged();
        App.Nav = CurrentPage.Navigation;
    }
}

Now, whenever you are calling the Navigation property using App class, the active stack is going to be chosen.

Extras

The basic idea of navigation is simple. You have an overall context, your App class, which has the information about your current location and provides a mechanism to interact with it. You can use this mechanism from anywhere inside the application to go forward and backward in the navigation stack. Classes requesting such manipulations do not have any knowledge about the selected tab or current position in its navigation stack. All they need to do is ask the App to perform a specific operation on the right stack.

Depending on your project, you might need some extra functionalities in connection to your TabbedPage. Here are two optional steps we had to use in our app:

Step 6.(Optional) Reset all stacks
Using the concept of separate navigation trees inside a TabbedPage, you might also want to be able to reset all of them at once. For instance, if a user logs out, you would not want the app to keep all the navigation stacks for the next logged in user to see. What you would need here is to call PopToRootAsync() method on the Navigation property of each one of your TabbedPage children.

Step 7. (Optional) Set selected tab
Sometimes it can also be useful to define selected tab programmatically. In our case, a user should always land on a specific tab after login. To achieve this you can set the CurrentPage property of your TabbedPage.

I guess that would be it for our short dive into the unwonted peculiarities of cross-platform development. I hope this post was useful for some of you Xamarin warriors out there. Just like any of us here at Billige Plätze, I would be very happy to hear from you in the comment section below. Any feedback is appreciated.

Let’s learn from each other.
Dannynator.

]]>
https://craftcoders.app/xamarin-tabbed-page-navigation/feed/ 0