Passing data between views via segmented bar - ios

I know this is a frequently asked question throughout the forums and I hate to ask another seemingly simplistic question, but I can't manage to find a solution on data passing in regards to my specific circumstance.
Basically, I have a view controller embedded within a navigation controller; of which displays a segmented bar, acting as a 'profile selector'. After selection, I want a series of images on another view to be changed after different profiles are selected, but the data passing isn't seem to be working whatsoever. I'm unsure if delegate is required within my specific circumstance.
Essentially; I'd just love an example of how to pass a case value for a segmented bar so I would be able to perform a simple case statement like follows: (where the case values have been passed from the previous view controller)
#IBAction func chooseImage(sender: AnyObject) {
switch myPhotoSegment.selectedSegmentIndex {
//if first segment selected
case 0:
//stop image animation if currently animating
newImageView.stopAnimating()
//update displayed image
newImageView.image = UIImage(named: "1.jpg")
//if second segment selected
case 1:
//stop image animation if currently animating
newImageView.stopAnimating()
//update displayed image
newImageView.image = UIImage(named: "2.jpg")
//if third segment selected
case 2:
//stop image animation if currently animating
newImageView.stopAnimating()
//update displayed image
newImageView.image = UIImage(named: "3.jpg")
//if fourth segment selected
case 3:
//stop image animation if currently animating
newImageView.stopAnimating()
//update displayed image
newImageView.image = UIImage(named: "4.jpg")
//by default, no segment selected
default:
newImageView.image = nil
}
}
I know my problem is long and probably poorly explained, but any feedback would be greatly appreciated. I kind of struggle with the whole logic and understanding of passing data, so if you could break down the solution for me as simply as possible; that would be incredible.

To share data between those two classes they either need to know about each other or know about a shared object. For example, you could create a singleton data model that contains the properties you want to pass back and forth.
private let instance = MySingleton()
class MySingleton {
var somethingImInterestedIn: String?
var sharedInstance: MySingleton {
get {
return instance
}
}
}
Each class would get a reference to this by using:
MySingletion.sharedInstance
Once they have the reference to the singleton they can set or get any of the properties in that object. For your case you would want to store an enum instead of a String.

Related

How to add network fetch inside swift struct initializer

I'm trying to get data from an URL (1st network call), then make a 2nd network call to fetch image from the url in the first response, and present on tableview in swift.
The json from 1st network call contains 3 strings:
struct Item: Codable {
var title: String?
var image_url: String?
var content: String
}
What I need to do is, after json decoding and get this [Item] array, I also need to make network call for each item in it so I can also get the UIImage for each of them and present title, content and Image on each cell.
My question is, where is the best place to make the network call (in MVVM pattern)?
My first thought is in the TableViewCell:
func configCell(item: Item) {
titleLabel.text = item.title
descriptionLable.text = item.content
// fetch image
service.fetchImage(with: item.image_url) { result in
switch result {
case .success(let image):
DispatchQueue.main.async { [weak self] in
if let image = image {
self?.iconView.isHidden = false
}
}
case .failure(let error):
print(error.localizedDescription)
return
}
}
}
However, I'm not sure if this the right way/place to do so because it might cause wrong image attach to each cell. Also it couples network layer into view layer.
So my 2nd thought is, create a second model in the viewModel and make network call in it:
struct ImageItem: Codable {
var title: String?
var image_url: String?
var content: String
var image: Data?
init(with item: Item) {
self.title = item.title
self.content = item.content
ContentService().fetchImage(with: item.image_url) { result in
switch result {
case .success(let image):
self.image = image?.pngData()
case .failure(let error):
print(error.localizedDescription)
}
}
}
But that doesn't feel right either. Also seems like I can't make network call in struct initializer.
Could anyone help or give me advice about where to put this 2nd layer network call for fetching the image?
If those images are lightweight, you can do it directly inside cellForRow(at:) method of UITableViewDataSource. If using a library/pod such as Kingfisher - it's just 1 line that takes care of of, plus another one to import the library:
import Kingfisher
...
cell.iconView.kf.setImage(with: viewModel.image_url)
Otherwise, you may have to take care yourself of caching, not downloading images unless really needed etc.
As long as the image download/configuration is done in UIViewController and not inside the Custom Cell, it should be all good. But still make sure you use some protocol and separate Repository class implementing that protocol and containing the image download functionality, because the View Controller should be more like a Mediator and not be loaded with too much code such as networking and business logic (VCs should act more like view, and generally speaking it's not their job to get the remote image for your custom cells, but for simpler apps that should be ok, unless you want to over-engineer things :) ).
Some people argue that View Models should be light weight and therefore structs, while others make them classes and add lots of functionality such as interacting with external service. It all depends of what variation of MVVM you are using. Personally I prefer to simply keep a URL property in the View Model and implement the "getRemoteImage(url:)" part outside the custom cell, but I've seen people adding UIImage properties directly to the View Models or Data type properties and having some converters injected that transform Data -> UIImage.
You can actually have a Repository and inject the Networking code to the View Model, and then add an Observer<UIImage?> inside the View Model and bind the corresponding cells' images to those properties via closures so the cell automatically updates itself on successful download (if still visible) or next time it appears on screen... Since each cell would have a corresponding view model - each individual view model can keep track if the image for IndexPath was already dowloaded or not.

Show or hide items by clicking button

I have four imageview contents in an XIB and a button that covers all my XIB. I want to make when the user tap the button, the first imageview is shown, the next tap is hidden and the second imageview is displayed and so on until all my imageview is shown / hidden. What would be the most efficient way to do it?
Save all your UIImageViews to an array, and current showing imageView to a variable, it may look like this:
var imageViews: [UIImageView] = []
var currentImageViewIndex = 0 {
didSet {
if currentImageViewIndex >= imageViews.count { currentImageViewIndex = 0 }
imageViews[oldValue].isHidden = true
imageViews[currentImageViewIndex].isHidden = false
}
}
func handleTap() {
currentImageViewIndex += 1
}
I suggest you use a state variable that contains an enum listing the various states (firstImageVisible, secondImage.... ) then you can have a function inside the enum that switches to the nextState (being the target of your button action) you can also easily iterate through states of an enum, check the documentation for the CaseIterable protocol. Often having a property observer (didSet) on the state is a handy place to update other parts of the UI which need to change every time the state changes.

How to use an array over multiple view controllers?

I have been playing around with a lot of stuff involving arrays and scrollviews. I have mostly stayed within the confines of view controllers, so usually i'll grab data from firebase, add it to an array, and then send it to the tableview or collectionview. What I'm trying to do now is actually navigate between viewcontrollers (or multiple copies of the same view controller) and applying the array items to each view controller.
For example I want to be able to grab some photos from firebase, put them in an array of url strings or whatever. Then I want to put a photo on the background of a view controller. Then when I push the over button it goes navigates to the next view controller and puts the next photo as the background there, etc.
I understand there are probably multiple ways to do this and I was wondering what is the most efficient way? Do I just put an array in a Global class and access that from all the view controllers? Or do I just keep the array in the first view controller, then as I navigate, keep sending it to the next view controller over and over? Also there will be a LOT of items and objects and arrays here so that's why I'm looking for efficiency. Thanks in advance to anyone able to help with this, and I hope I explained it well enough!
This is a very simple way of adding and retrieving String value from a struct, here you are saving the image url string as a value in a dictionary and it's key is going to be the ViewController name.
struct SavedData {
static private var imagesDictionary: [String: String] = [:]
static func image(for viewController: UIViewController) -> String? {
return imagesDictionary["\(type(of: viewController))"]
}
static func add(image name: String, for viewController: UIViewController) {
self.imagesDictionary["\(type(of: viewController))"] = name
}
}
saving a value is very simple, if you're saving the data in a viewController and you want a specific image to be saved for that viewController you can use self
SavedData.add(image: "img1.png", for: self)
And if you want to save an image for a different viewController, do it like this.
SavedData.add(image: "img2.png", for: SecondViewController())
Retrieving the image is also very simple, you should call this method in the viewController that you want to assign the image to.
let savedImage = SavedData.image(for: self)
print(savedImage!)

iOS - How to use a small view in different view controllers in Swift

I have a progress bar (with its own controller). This bar is supposed to be shown in different views depending on which view is visible. As the progress will be same, If possible I don't want to create many progress bar in many views rather I want to use same instance in all these views. Also in that way when I need to change any property of the progress bar it will be reflected commonly, which is required.
Please suggest me how can I use this common view. And also if my strategy is wrong, what would be the better design for such scenarios.
1) Well you have 2 options. You can declare a new Class ViewBox (or whatever name) and then use that inside your code
First View Controller
var box:ViewBox = ViewBox()
When you segue or transition to your next screen, you can have a predefined variable var box:ViewBox!. Then say when you press a button, the button has a function called transition.
//Now setup the transition inside the Storyboard and name the identifier "toThirdViewController"
override func prepareForSegue(segue:UIStoryboardSegue, sender:AnyObject?) {
if(segue.identifier == "toThirdViewController") {
var vc = segue.destinationViewController as! `nextViewController` //The class of your next viewcontroller goes here
vc.box = self.box
}
//Since The SecondViewController doesn't need ViewBox, we don't need it there.
}
where
nextViewController:UIViewController {
var box:ViewBox!
}
Or you could do a much simpler way and that is to look up a UIPageViewController :)

Dynamic updating of label/button title in Swift

I want to update the title property of a user interface element on iOS using Swift. In this case it is a UIBarButton, but it could be a UILabel, UIButton or whatever. Currently I am using this code, which works:
func setStatusMessage(barButton: UIBarButtonItem) {
let currentVersion = StatusModel.getCurrentVersion()
var statusUpdates = [StatusModel]()
var statusForCurrentVersion: StatusModel!
var statusMessage = String()
// check if update required before setting the text
checkIfLocalStatusNeedsUpdate()
barButton.title = getLocalStatusMessage()
// Try to update status anyway...
getStatusFromRemoteSource { (statusUpdates) -> Void in
for status in statusUpdates {
if status.version == currentVersion {
statusForCurrentVersion = status
}
}
self.saveStatusFromRemoteSource(statusForCurrentVersion)
barButton.title = statusForCurrentVersion.message
}
}
Although effective, this solution is ugly too as it does require a user interface (view) element to be embedded in my model. Not exactly a sort of MVC beauty.....
I cannot simply use return because the local status will be returned before the remote status can be fetched. So I guess I need some kind of handler / listener / delegate (/*getting lost here*/) to update the view dynamically. In this case that means: set the title using the locally stored value and update it if a remote value is received.
What is the best way to approach this scenario in a MVC compliant way, removing UI elements from the model code (thereby increasing reusability)?
One intermediate step you could do is going over the controller to the UI, by replacing the barButton parameter with a reference to the controller
barButton.title = statusForCurrentVersion.message
->
self.controller.update(title: statusForCurrentVersion.message)
This way your code is allowed to grow (updating more labels etc), but it comes with a cost of more code and harder readability.

Resources