Use of a view controller does not inherently violate the SRP. It's just really easy to make that happen because on iOS they are the most prominent item in the nominally MVC app architecture. What goes in a View and a Model seems obvious; they're dumb. Then all you have left is Controller. You have a (View) Controller because that's required by the system, and if you're not thinking, you just stick all the things there.
Apple's sample code doesn't display great architectural characteristics because it's written to demonstrate a small piece of framework functionality, not as a blueprint for an app. Apple doesn't necessarily want you to design your app poorly; they just have other priorities. Even some good books on iOS largely ignore design guidelines like separation of concerns in order to succinctly present information about the system.
It's certainly possible to define a single responsibility for a view controller: it mediates between its view and the data represented by the view. (See also: MVC: Does the Controller break the Single Responsibility Principle?) That means that it has two activities: to interpret input from the view, and to configure the view for display. Anything past those steps should be farmed out as quickly as possible to other objects.
For example, the view receives a tap in a particular subview. The view tells the controller, and the controller interprets that message as representing the user's desire to download a picture. The view controller should now immediately route that user request to a "downloader" component, which is actually responsible for that action. The view controller might also need to reconfigure the view, or it might wait for something to come back from its helper object first.
Either way, the view controller is not doing anything other than translating between its view and and other parts of the app. This should be a few lines at most.
The problem you're concerned about is well known among thoughtful iOS engineers: it's known as "Massive View Controller" (as a take-off of "Model View Controller"). Soroush Khanlou has some really excellent articles on avoiding the trap.
In iOS work, the two major schemes for avoiding Massive View Controller syndrome are called MVVM and VIPER.
Under MVVM -- probably best laid out in an article at objc.io -- the view controller and view are considered a single unit. A separate View Model holds "presentation logic": the details of how to transform the raw data from the model into the information the view needs. It's still the controller that configures the view, though.
As a concrete example, the model might have an "alert level": just a raw value. The view model will translate that into a string describing the alert, and a color. The view controller takes the string and puts it into the appropriate label and applies the color to a particular view element. It doesn't know how the string and color were calculated, or where they came from, or how often they're going to change, and it doesn't care. All it does know is where to put them. Any logic is has is strictly view-oriented: whether to perform an animation, what a constraint's constant should be.
The VIPER five-component architecture is also introduced at objc.io. Here again, the view controller is considered part of the View, but a lot of what might be its responsibility under generic MVC is now broken out into two other objects: the Interactor and Presenter.
The Interactor responds to user input to manipulate model data. The Presenter is similar to the View Model above: it transforms the model data into view attributes. (In MVVM, the View Model would do both of these things.)
The key view controller-slimming piece in VIPER, to my mind, is the "Router", sometimes called a "Wireframe". It's essentially responsible for transitioning between views. This really takes a lot of the potential for complexity and extra responsibility out of view controllers;* they no longer have to figure out what comes next when their task is done. Even if you don't want to strictly adopt VIPER, I'd highly suggest examining this idea.
*To some extent using segues also solves this, but segues come with flexibility problems. Most notably, you are forced to use
initWithCoder: rather than being able to make custom initializers for your view controllers.