Multiple ViewControllers with same background - ios

How can one still background image in multiple view controllers be implemented?
With self.view.backgroundColor = UIColor(patternImage: UIImage(named: "file.png")) background would move along with UIViewController while screen switching.
Is it possible to place image on separate layer under view controllers?

Solution 1
Create a class called MyBackground:
class MyBackground {
class func addBackgroundImage() {
UIGraphicsBeginImageContext(self.view.frame.size)
UIImage(named: "file")?.drawInRect(self.view.bounds)
let image: UIImage = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
view.backgroundColor = UIColor(patternImage: image)
}
}
If you want to call it in your AViewController, simply call MyBackground.addBackgroundImage()
Solution 2
Create a ViewController called MyViewController:
class MyViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
addBackgroundImage()
}
func addBackgroundImage() {
UIGraphicsBeginImageContext(self.view.frame.size)
UIImage(named: "file")?.drawInRect(self.view.bounds)
let image: UIImage = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
view.backgroundColor = UIColor(patternImage: image)
}
}
If you AViewController want to set this image, simply do like this:
AViewController: MyViewController //AViewController is a subclass of MyViewController
For example: I have an image call background, and I will set it as a background image of my ViewController:
If I just add it in one line as #startupthekid said:
If I add it as I said:

i think you can set background in your container view controllers, such as navi controller, tab controller, then set all your view controller's background to clear color.

A better answer is to use UIAppearance. If you're not familiar with it, UIAppearance is a proxy protocol that you send messages to and it modifies later instances of that class.
For example if I have a custom button class RoundButton:
RoundButton.appearance().titleFont = Font.Heavy(20)
And in my RoundButton class:
dynamic var titleFont: UIFont? {
get { return self.titleLabel?.font }
set { self.titleLabel?.font = newValue }
}
The dynamic keywords is incredibly important. It's used for objective-c compatibility and setting properties via UIAppearance that don't use it will cause a crash.
You can do as many custom properties as you want and there's a bunch already default in the os.
In your case you can do something like:
UIImageView.appearanceWhenContainedInInstancesOfClasses([MyCustomViewController.self)].customImage = UIImage(...)
You can of course get away with just a custom view controller but in my opinion, UIApperance keeps your UI code separate from your controller logic.
One way you could implement the behavior you want is a UIViewController subclass:
class MyCustomViewController: UIViewController {
private let backgroundImageView = UIImageView()
dynamic var backgroundImage: UIImage? {
get { self.backgroundImageView.image }
set { self.backgroundImageView.image = newValue }
}
}
and then modify the backgroundImage property on the appearance instance of MyCustomViewController.
MyCustomViewController.appearance().backgroundImage = ...
EDIT: Given that your question was misleading and that you instead want a shared image that never moves throughout all your view controllers, you can simply insert an image view into the application window:
if let window = UIApplication.sharedApplication().windows.first {
let imageView = UIImageView(image: UIImage(named: "face-mask"))
window.addSubview(imageView)
imageView.frame = window.bounds
}
If you like Autolayout, like I do, then I'd set constraints to pin the image view to the window edges.

you should set the image in viewDidload of every viewController
Update :
either you should set in every view controller or you can set to windows . Second thing if your background changes dynamically with different images then you can use prepareforsegue method to pass images to nextviewcontroller if images are different for each viewcontroller. if same image for every viewcontroller then you can set it to window.
:)

Related

How do I add an identifier to the UIImageView?

I have an array with separate UIImages and I am displaying them using an UIImageView, how do I add an identifier to them so that I can switch to the view controller with the specific data related to that image.
Below is the code:
class Orders2ViewController: UIViewController, OrdersBaseCoordinated {
var orderList: [OrderInfo] = [OrderInfo( itemImage: UIImage(named: "printer.jpeg")!, itemSKU: 12567), OrderInfo(itemImage: UIImage(named: "ipad.jpeg")!, itemSKU: 34521), OrderInfo( itemImage: UIImage(named: "hoodie.jpeg")!, itemSKU: 93620)]
lazy var someImageView: UIImageView = {
let theImageView = UIImageView()
let tap = UITapGestureRecognizer(target: self, action: #selector(Orders2ViewController.tappedMe))
theImageView.addGestureRecognizer(tap)
theImageView.isUserInteractionEnabled = true
theImageView.image = orderList[selectedIndex].itemImage
theImageView.translatesAutoresizingMaskIntoConstraints = false
return theImageView
}()
#objc func tappedMe(sender: UITapGestureRecognizer)
{
coordinator?.passImageData(j: )
}
I want to pass an identifier in my tappedMe function to recognize which image was tapped on, I did find other answers on SO that mentioned gesture.view.tag but I don't want to create another view, rather navigate to the next controller with an identifier. Is there a way to do this?
Since UIImageView inherits from UIView, you can use it’s parameter called tag. In your example you can set theImageView.tag = orderedList[selectedIndex].itemSKU. Then each time tap was recognized, you can just use this itemSKU to do move to the next screen.
However it seems that you have only one UIImageView on the screen and you use selectedIndex to determine which image you need. So you can just do like that:
#objc func tappedMe(sender: UITapGestureRecognizer) {
coordinator?.passImageData(j: orderList[selectedIndex].itemImage)
}
I passed the itemImage just to show you how it can be done. You can use whatever you need.

Share Extension - Remove Textfield

Is there a way to remove the TextView- and PreviewPart, marked in RED, from my ShareExtention?
Im using the default:
class ShareViewController: SLComposeServiceViewController
As ViewController
I want to just show the Destination Selector like below (I know how to create that)
I know that you can create your own ViewController but it would be very hard to rebuild the default one.
The Second thing is:
What method would you recommend to Transfer/Copy Data/Files and which to read the Main App's Document Directory?
I want to transfer/copy the selected Document to the DocumentsDirectory of my Main App. Both are in the same AppGroup. Bc if I just save the Url of the current Document in UserDefault, then I guess the Main App can't access it.
I need to read the Main App's Document Directory, because I need the file Hierachy so I can select the saving location.
Note: Just because there was some confusion in the comments for whatever reason: This is my own ShareExtention and not smo else's in UIActivityController
Not the best, but here's what I've got based on the docs:
override func viewDidLoad() {
super.viewDidLoad()
textView.isHidden = true
}
But then there's blank space. Perhaps it's better to put some placeholder text in and prevent user input. There are other options in the docs you can use for that, or you can mess with textView (which is a UITextView)...
Edit: Here's a complete example that shows the URL in the text field, grey and non-user-interactible.
class ShareViewController: SLComposeServiceViewController {
override func viewDidLoad() {
super.viewDidLoad()
textView.isUserInteractionEnabled = false
textView.textColor = UIColor(white: 0.5, alpha: 1)
textView.tintColor = UIColor.clear // TODO hack to disable cursor
getUrl { (url: URL?) in
if let url = url {
DispatchQueue.main.async {
// TODO this is also hacky
self.textView.text = "\(url)"
}
}
}
}
override func isContentValid() -> Bool {
// Do validation of contentText and/or NSExtensionContext attachments here
return true
}
func getUrl(callback: #escaping ((URL?) -> ())) {
if let item = extensionContext?.inputItems.first as? NSExtensionItem,
let itemProvider = item.attachments?.first as? NSItemProvider,
itemProvider.hasItemConformingToTypeIdentifier("public.url") {
itemProvider.loadItem(forTypeIdentifier: "public.url", options: nil) { (url, error) in
if let shareURL = url as? URL {
callback(shareURL)
}
}
}
callback(nil)
}
override func didSelectPost() {
getUrl { (url: URL?) in
if let url = url {
DispatchQueue.main.async {
print("url: \(url)")
}
}
}
}
override func configurationItems() -> [Any]! {
// To add configuration options via table cells at the bottom of the sheet, return an array of SLComposeSheetConfigurationItem here.
return []
}
}
What worked for me is to use a custom solution instead of SLComposeServiceViewController. It looks the same, and can add any element I want (see image at the end and here's the code at the commit when it was implemented).
Steps:
(code) Change ShareViewController to simple UIViewController
(code) Add blur effect to ShareViewController
(storyboard) Add container view to ShareViewController
(storyboard) Add navigation controller
(storyboard) Embed navigation controller in ShareViewController's container view
Customize the view controllers in the navigation controller (see this SO thread for example)
Step 1. Change ShareViewController to simple UIViewController
import UIKit
class ShareViewController: UIViewController {
// ^^^^^^^^^^^^^^^^
Step 2. Add blur effect to ShareViewController
// ShareViewController continued from Step 1.
override func viewDidLoad() {
super.viewDidLoad()
// https://stackoverflow.com/questions/17041669/creating-a-blurring-overlay-view/25706250
// only apply the blur if the user hasn't disabled transparency effects
if UIAccessibilityIsReduceTransparencyEnabled() == false {
view.backgroundColor = .clear
let blurEffect = UIBlurEffect(style: .dark)
let blurEffectView = UIVisualEffectView(effect: blurEffect)
//always fill the view
blurEffectView.frame = self.view.bounds
blurEffectView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
view.insertSubview(blurEffectView, at: 0)
} else {
view.backgroundColor = .black
}
// Do any additional setup after loading the view.
}
Step 3. Add container view to ShareViewController
Drag a Container View from the Object Library into the ShareViewController on the storyboard, and adjust dimension. For example:
Step 4. Add navigation controller
Drag a Navigation Controller from the Object Library to the storyboard.
Step 5. Embed navigation controller in ShareViewController's container view
Control-drag from the container view of ShareViewController to the navigation controller, select "Embed" from the menu. Should look similar to this:
Step 6. Customize the view controllers in the navigation controller (see this SO thread for example)
My result:
You can't.
When you use the share extension you will afterwards select an app to share to, thus plugging yourself to the share extension of said app.
Your app does not and cannot have any access of what said share extension does, those are totally separated from the main app runtime.
Also this allow users to customise what they share.
For the second part of your post you need to elaborate, who wants to transfer a document, you as the developper or your end-user?

EXC_BAD_ACCESS when setting properties of an option view property

I'm trying to figure out how to make a UIProgessView (loading Bar) have the color tint of green. I've looked around and there is no working Swift version for this. I am also trying to figure out how to take the bar off of the screen when it's finished. Nothing is done with the storyboard, everything is done programmatically.
I'm trying to customize the bar with this but it says "Bad access".
self.progressView!.tintColor = UIColor.greenColor()
This is where I'm trying to hide the bar, but there is a bad access here as well.
progressView!.hidden = true
The context:
import UIKit
import AVFoundation
class MainController: UIViewController {
var progressView: UIProgressView?
override func viewDidLoad() {
super.viewDidLoad()
addControls()
}
func addControls() {
//----This where it try to change the tint below
self.progressView!.tintColor = UIColor.greenColor()
// Create Progress View Control
progressView = UIProgressView(progressViewStyle: UIProgressViewStyle.Default)
progressView?.center = self.view.center
view.addSubview(progressView!)
}
}
You haven't actually created the UIProgressView before you call self.progressView!.tintColor thus, progressView is nil at this point but you're trying to force unwrap it.
Move the progressView = UIProgressView(...) line up to the top of addControls() and that should solve the problem.

Theres is a way to be notified in swift if an UIImageView image has been set?

if UIImageView were my class I would have solved this by doing:
class myUIImageView{
var image : UIImage? {
didSet {
if self.image != nil{
//do my stuff
}
}
}
//Other fields and functions
}
What I want is to be notified (Observer Like)
I did find the answer... Swift allows to override var so there is no real complication here. Just change where you use UIImageView to be using myUIImageView.
class myUIImageView : UIImageView{
override var image: UIImage?{
didSet {
if image != nil{
//do your stuff (add effects, layers, ...)
} else {
//clean your filter or added layer (remove your effects over the view)
}
}
}
}
UPDATE
Be advice, if your UIImageView is part of a Collection View you should remove the effect when the collection reuses your view. Otherwise it will remain visible even if no image is being shown.
You should treat a view controller's views as private. The only object that should be changing a UIImageView is the UIViewController it belongs to.
Assuming you do that, then the view controller can do whatever you want when it changes the image view's image.

Adding View Programmatically from ViewController super class doesn't show in front

I have a BaseViewController that my UIViewControllers extend so i can have explicit functions that i dont need to rewrite. Something i would like would be a functions such as self.showSpinner() and the viewController would show the spinner
My Code looks like this
class BaseViewController: UIViewController {
var actvIndicator : UIActivityIndicatorView!
override func viewDidLoad() {
super.viewDidLoad()
self.actvIndicator = UIActivityIndicatorView(activityIndicatorStyle: .WhiteLarge)
self.actvIndicator.color = UIColor.blackColor()
self.actvIndicator.backgroundColor = UIColor.blackColor()
self.actvIndicator.frame = CGRectMake(self.view.frame.size.width / 2, self.view.frame.size.height / 2, 100, 100);
self.actvIndicator.center = self.view.center
self.actvIndicator .startAnimating()
self.view.addSubview(self.actvIndicator)
self.actvIndicator.bringSubviewToFront(self.view)
self.edgesForExtendedLayout = UIRectEdge.None
self.navigationController?.navigationBar.translucent = false
}
func showSpinner(){
self.actvIndicator.startAnimating()
}
func hideSpinner(){
self.actvIndicator.stopAnimating()
}
}
And my viewcontrollers looks like this
class MyProjectViewController: BaseViewController {
override func viewDidLoad() {
super.viewDidLoad()
self.showSpinner()
}
}
MyProjectViewController have UITableView that fills the entire screen. When i set tblProjects.alpha = 0 i can see the spinner. But i want it in the front.
i also tried self.view.bringSubviewToFront(self.actvIndicator)
What am i missing?
A couple quick notes before I get into what I think your problem is:
When you add a subview it is automatically added to the top layer, no need for the bringSubviewToFront: in viewDidLoad: (which is being used wrong anyway).
You should not set view frames in viewDidLoad: (e.g. centering a view). Frames are not setup yet, so you should move that to viewWillAppear: or some other variant.
Now your issue is most likely a view hierarchy problem (further confirmed by your comment) and thus can probably be fixed by pushing the spinner to the front every time you want it to be shown, like:
func showSpinner() {
self.view.bringSubviewToFront(self.actvIndicator)
self.actvIndicator.startAnimating()
}
The problem here stands on the fact that table view is draw after you are calling self.view.bringSubviewToFront(self.actvIndicator). A possible workaround for this is to call bringSubviewToFront when showing the spinner
func showSpinner(){
self.view.bringSubviewToFront(self.actvIndicator)
self.actvIndicator.startAnimating()
}

Resources