I don't know why this happens. I've a UIViewController with a UITableView where i would like to add a search bar for querying my server using the new UISearchController (Swift 3, Xcode 8.1).
I've tried everything like add the search bar in Interface builder, set search bar background to UIImage but nothing works.
This is the view controller's class barebone:
class MyViewController: UIViewController {
var searchController = UISearchController(searchResultsController: nil)
fileprivate func setupSearchController() {
searchController.searchResultsUpdater = self
searchController.dimsBackgroundDuringPresentation = false
searchController.searchBar.barTintColor = .myColor()
searchController.searchBar.tintColor = .white
searchController.searchBar.backgroundColor = .myColor()
searchController.searchBar.placeholder = "searchSomething"
searchController.searchBar.delegate = self
definesPresentationContext = true
tableView.tableHeaderView = searchController.searchBar
}
}
extension MyViewController: UISearchResultsUpdating, UISearchBarDelegate {
// delegates methods...
}
Maybe it's something regarding my design pattern but i'm not really sure as UISearchController is really new and i haven't found any good examples on the web regarding its use.
You can set a border around the search bar the same red color as the nav.
searchBar.layer.borderColor = (your red color).cgColor
searchBar.layer.borderWidth = 1
Related
I'm using the latest beta of Xcode 13 with an app for iOS 14 and now I'm facing this strange issue:
The global accent color of my app was working fine until the iOS 15 update when the color is now set as the default blue where before it was my custom color.
Here is the asset catalog:
This is my project settings page where you can see that the accent color is correct.
And this is what the app looks like when built. The color is the default blue when it needs to be a really dark blue/purple color.
We were using the UIAppearance API in our app. We were not setting the tint color, but somehow calling any of the UIAppearance API's after the app has finished launching causes this behavior.
enum AppAppearance {
static func configure() {
configureCustomBarApperance()
UITableView.appearance().backgroundColor = UIColor(named: .primaryBackground)
UITextView.appearance().backgroundColor = nil
UIScrollView.appearance().keyboardDismissMode = .interactive
}
static func configureCustomBarApperance() {
let barAppearance = UIBarAppearance()
barAppearance.configureWithTransparentBackground()
barAppearance.backgroundColor = UIColor(named: .primaryBackground)
// Toolbars
let toolbarAppearance = UIToolbarAppearance(barAppearance: barAppearance)
UIToolbar.appearance().standardAppearance = toolbarAppearance
UIToolbar.appearance().compactAppearance = toolbarAppearance
UIToolbar.appearance().scrollEdgeAppearance = toolbarAppearance
// Navigation Bars
let navBarAppearance = UINavigationBarAppearance(barAppearance: barAppearance)
navBarAppearance.titleTextAttributes[.foregroundColor] = UIColor.secondaryLabel
UINavigationBar.appearance().standardAppearance = navBarAppearance
UINavigationBar.appearance().compactAppearance = navBarAppearance
UINavigationBar.appearance().scrollEdgeAppearance = navBarAppearance
// Tab Bars
let tabBarAppearance = UITabBarAppearance()
tabBarAppearance.configureWithTransparentBackground()
tabBarAppearance.backgroundColor = UIColor(named: .secondaryBackground)
UITabBar.appearance().standardAppearance = tabBarAppearance
UITabBar.appearance().scrollEdgeAppearance = tabBarAppearance
}
}
Our solution was to move all of the UIAppearance API work to the initializer of the AppDelegate and this fixed the issue for us. So instead of calling AppAppearance.configure() after the app finished launching...we call it from AppDelegate.init and our Global Accent Color is now being honored.
Don't ask me why...I couldn't tell you.
I finally found a temporary workaround on this AppleDeveloperForum Thread
credit to: #chad_sykes for this answer
I found an alternate solution, which was to set the .accentColor on the main view in the WindowGroup and it gets used across the app.
#main
struct CurvApp: App {
var body: some Scene {
WindowGroup {
myMainView
.accentColor(CurvGlobalAppearance.curvAccentColor)
}
}
}
Is there a more efficient way to animate text shivering with typewriting all in one sklabelnode? I'm trying to achieve the effect in some games like undertale where the words appear type writer style while they are shivering at the same time.
So far I've only been able to achieve it but with such luck:
class TextEffectScene: SKScene {
var typeWriterLabel : SKLabelNode?
var shiveringText_L : SKLabelNode?
var shiveringText_O : SKLabelNode?
var shiveringText_S : SKLabelNode?
var shiveringText_E : SKLabelNode?
var shiveringText_R : SKLabelNode?
var button : SKSpriteNode?
override func sceneDidLoad() {
button = self.childNode(withName: "//button") as? SKSpriteNode
self.scaleMode = .aspectFill //Very important for ensuring that the screen sizes do not change after transitioning to other scenes
typeWriterLabel = self.childNode(withName: "//typeWriterLabel") as? SKLabelNode
shiveringText_L = self.childNode(withName: "//L") as? SKLabelNode
shiveringText_O = self.childNode(withName: "//O") as? SKLabelNode
shiveringText_S = self.childNode(withName: "//S") as? SKLabelNode
shiveringText_E = self.childNode(withName: "//E") as? SKLabelNode
shiveringText_R = self.childNode(withName: "//R") as? SKLabelNode
}
// Type writer style animation
override func didMove(to view: SKView) {
fireTyping()
shiveringText_L?.run(SKAction.repeatForever(SKAction.init(named: "shivering")!))
shiveringText_O?.run(SKAction.repeatForever(SKAction.init(named: "shivering2")!))
shiveringText_S?.run(SKAction.repeatForever(SKAction.init(named: "shivering3")!))
shiveringText_E?.run(SKAction.repeatForever(SKAction.init(named: "shivering4")!))
shiveringText_R?.run(SKAction.repeatForever(SKAction.init(named: "shivering5")!))
}
let myText = Array("You just lost the game :)".characters)
var myCounter = 0
var timer:Timer?
func fireTyping(){
typeWriterLabel?.text = ""
timer = Timer.scheduledTimer(timeInterval: 0.5, target: self, selector: #selector(TextEffectScene.typeLetter), userInfo: nil, repeats: true)
}
func typeLetter(){
if myCounter < myText.count {
typeWriterLabel?.text = (typeWriterLabel?.text!)! + String(myText[myCounter])
//let randomInterval = Double((arc4random_uniform(8)+1))/20 Random typing speed
timer?.invalidate()
timer = Timer.scheduledTimer(timeInterval: 0.2, target: self, selector: #selector(TextEffectScene.typeLetter), userInfo: nil, repeats: false)
} else {
timer?.invalidate() // stop the timer
}
myCounter += 1
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
let touch = touches.first
if let location = touch?.location(in: self) {
if (button?.contains(location))! {
print("doggoSceneLoaded")
let transition = SKTransition.fade(withDuration: 0.5)
let newScene = SKScene(fileNamed: "GameScene") as! GameScene
self.view?.presentScene(newScene, transition: transition)
}
}
}
}
As you can see, I had to animate each individual label node in a word "loser".
To create this effect:
For those who may be interested to Swift 4 I've realized a gitHub project around this special request called SKAdvancedLabelNode.
You can find here all sources.
Usage:
// horizontal alignment : left
var advLabel = SKAdvancedLabelNode(fontNamed:"Optima-ExtraBlack")
advLabel.name = "advLabel"
advLabel.text = labelTxt
advLabel.fontSize = 20.0
advLabel.fontColor = .green
advLabel.horizontalAlignmentMode = .left
addChild(self.advLabel)
advLabel.position = CGPoint(x:frame.width / 2.5, y:frame.height*0.70)
advLabel.sequentiallyBouncingZoom(delay: 0.3,infinite: true)
Output:
something i have a lot of experience with... There is no way to do this properly outside of what you are already doing. My solution (for a text game) was to use NSAttributedString alongside CoreAnimation which allows you to have crazy good animations over UILabels... Then adding the UILabels in over top of SpriteKit.
I was working on a better SKLabel subclass, but ultimately gave up on it after I realized that there was no way to get the kerning right without a lot more work.
It is possible to use an SKSpriteNode and have a view as a texture, then you would just update the texture every frame, but this requires even more timing / resources.
The best way to do this is in the SK Editor how you have been doing it. If you need a lot of animated text, then you need to use UIKit and NSAttributedString alongside CoreAnimation for fancy things.
This is a huge, massive oversight IMO and is a considerable drawback to SpriteKit. SKLabelNode SUCKS.
As I said in a comment, you can subclass from an SKNode and use it to generate your labels for each characters. You then store the labels in an array for future reference.
I've thrown something together quickly and it works pretty well. I had to play a little bit with positionning so it looks decent, because spaces were a bit too small. Also horizontal alignement of each label has to be .left or else, it will be all crooked.
Anyway, it'S super easy to use! Go give it a try!
Here is a link to the gist I just created.
https://gist.github.com/sonoblaise/e3e1c04b57940a37bb9e6d9929ccce27
I've read the documentation, gone through their wonderful Playground example, searched S.O., and reached the extent of my google-fu, but I cannot for the life of me wrap my head around how to use ReactiveSwift.
Given the following....
class SomeModel {
var mapType: MKMapType = .standard
var selectedAnnotation: MKAnnotation?
var annotations = [MKAnnotation]()
var enableRouteButton = false
// The rest of the implementation...
}
class SomeViewController: UIViewController {
let model: SomeModel
let mapView = MKMapView(frame: .zero) // It's position is set elsewhere
#IBOutlet var routeButton: UIBarButtonItem?
init(model: SomeModel) {
self.model = model
super.init(nibName: nil, bundle: nil)
}
// The rest of the implementation...
}
....how can I use ReactiveSwift to initialize SomeViewController with the values from SomeModel, then update SomeViewController whenever the values in SomeModel change?
I've never used reactive anything before, but everything I read leads me to believe this should be possible. It is making me crazy.
I realize there is much more to ReactiveSwift than what I'm trying to achieve in this example, but if someone could please use it to help me get started, I would greatly appreciate it. I'm hoping once I get this part, the rest will just "click".
First you'll want to use MutableProperty instead of plain types in your Model. This way, you can observe changes to them.
class Model {
let mapType = MutableProperty<MKMapType>(.standard)
let selectedAnnotation = MutableProperty<MKAnnotation?>(nil)
let annotations = MutableProperty<[MKAnnotation]>([])
let enableRouteButton = MutableProperty<Bool>(false)
}
In your ViewController, you can then bind those and observe those however necessary:
class SomeViewController: UIViewController {
let viewModel: Model
let mapView = MKMapView(frame: .zero) // It's position is set elsewhere
#IBOutlet var routeButton: UIBarButtonItem!
init(viewModel: Model) {
self.viewModel = viewModel
super.init(nibName: nil, bundle: nil)
}
override func viewDidLoad() {
super.viewDidLoad()
routeButton.reactive.isEnabled <~ viewModel.enableRouteButton
viewModel.mapType.producer.startWithValues { [weak self] mapType in
// Process new map type
}
// Rest of bindings
}
// The rest of the implementation...
}
Note that MutableProperty has both, a .signal as well as a .signalProducer.
If you immediately need the current value of a MutableProperty (e.g. for initial setup), use .signalProducer which immediately sends an event with the current value as well as any changes.
If you only need to react to future changes, use .signal which will only send events for future changes.
Reactive Cocoa 5.0 will add UIKit bindings which you can use to directly bind UI elements to your reactive layer like done with routeButton in the example.
as we know, to implement PageTabBarController, we need to insert these code in AppDelegate.swift:
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions:
let viewControllers = [MatchDetailViewController(),ListPlayersViewController(),ChatViewController()]
window = UIWindow(frame: Device.bounds)
window!.rootViewController = MatchViewController(viewControllers: viewControllers, selectedIndex: 0)
window!.makeKeyAndVisible()
}
Now, i need to use PageTabBarController when i want to open detail for my match data. My question is, how to implement it without insert those code in AppDelegate.swift because it will open my MatchViewController (extend from PageTabBarController) for the first app launch.
I have tried this code, but it will cause Crash, and it pointed to my AppDelegate.swift
class MatchViewController: PageTabBarController {
var window: UIWindow?
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
open override func prepare() {
super.prepare()
let viewControllers = [MatchDetailViewController(),ListPlayersViewController(),ChatViewController()]
//1st try: Crash
window = UIWindow(frame: Device.bounds)
window!.rootViewController = MatchViewController(viewControllers: viewControllers, selectedIndex: 0)
window!.makeKeyAndVisible()
//2nd try: error
self.rootViewController = MatchViewController(viewControllers: viewControllers, selectedIndex: 0)
//3rd try: crash
self.viewControllers = viewControllers
delegate = self
preparePageTabBar()
}
fileprivate func preparePageTabBar() {
pageTabBar.lineColor = Color.blue.base
pageTabBar.dividerColor = Color.blueGrey.lighten5
pageTabBarAlignment = PageTabBarAlignment.top
pageTabBar.lineAlignment = TabBarLineAlignment.bottom
}
}
extension MatchViewController: PageTabBarControllerDelegate {
func pageTabBarController(_ pageTabBarController: PageTabBarController, didTransitionTo viewController: UIViewController) {
}
}
Linked GitHub Question
Hi, yes there is a way. The PageTabBarController is inherited from aUIViewController`, which allows you to add it as a child of any other UIViewController. That said, you just gave me a great idea. I am going to make a new UIViewController that allows you to add as many child UIViewControllers, which will make this super easy to do. I will make this as a Feature Request.
Until the update, please use the suggested method of adding it as a child UIViewController. Are you familiar with how to do that?
First create AppToolbarController (subclass of ToolbarController) or you can use the one in the Material library demo.
And then from your view controller, you can use:
DispatchQueue.main.async {
let tabbarViewController = AppPageTabbarController(viewControllers: [vc1,vc2,vc3], selectedIndex: 0)
self.present(AppToolbarController(rootViewontroller: tabbarViewController))
}
I've tried to expand the default Apple MasterDetail Template by adding a UITabbarController in front of the UINavigationController of the MasterView, so there is a structure like this:
UISplitViewController (Master) > UITabbarController > UINavigationController > UITableViewController
But if I run the App, after changing application(didFinishLaunchingWithOptions) to use the correct ViewController, and try to perform the ShowDetails Segue the DetailsView ist presented Modally on the iPhone. On the other side the iPad Version is working as expected.
What am I forgot to do? Or how can I fix it?
I figured out how to put the detail on to the master's UINavigationController instead of presenting it modally over the UITabBarController.
Using the UISplitViewControllerDelegate method
- splitViewController:showDetailViewController:sender:
In case the UISplitViewController is collapsed get the masters navigation controller and push the detail view onto this navigation controller:
- (BOOL)splitViewController:(UISplitViewController *)splitViewController
showDetailViewController:(UIViewController *)vc
sender:(id)sender {
NSLog(#"UISplitViewController collapsed: %d", splitViewController.collapsed);
// TODO: add introspection
if (splitViewController.collapsed) {
UITabBarController *master = (UITabBarController *) splitViewController.viewControllers[0];
UINavigationController *masterNavigationController = (UINavigationController *)master.selectedViewController;
// push detail view on the navigation controller
[masterNavigationController pushViewController:vc animated:YES];
return YES;
}
return NO;
}
Just to update the answers above. Since you can't push navigation controllers anymore, you have to push its top view controller instead.
func splitViewController(splitViewController: UISplitViewController, showDetailViewController vc: UIViewController, sender: AnyObject?) -> Bool {
if splitViewController.collapsed {
let tabBarController = splitViewController.viewControllers.first as! UITabBarController
let selectedNavigationViewController = tabBarController.selectedViewController as! UINavigationController
// Push view controller
var viewControllerToPush = vc
if let navController = vc as? UINavigationController {
viewControllerToPush = navController.topViewController
}
selectedNavigationViewController.pushViewController(viewControllerToPush, animated: true)
return true
}
return false
}
Here's my solution. Place in MasterViewController.m and remember to give your detail view a Storyboard ID in IB. In my case 'detail'.
-(BOOL)shouldPerformSegueWithIdentifier:(NSString *)identifier sender:(id)sender {
if ([identifier isEqualToString:#"showDetail"] && self.splitViewController.collapsed) {
DetailViewController *myController = (DetailViewController *)[self.storyboard instantiateViewControllerWithIdentifier:#"detail"];
NSIndexPath *indexPath = [self.tableView indexPathForSelectedRow];
NSManagedObject *object = [[self fetchedResultsController] objectAtIndexPath:indexPath];
[myController setDetailItem:object];
[self.navigationController showViewController:myController sender:self];
return NO;
}
return YES;
}
There is another way to do it without code.
After you embedded the the UINavigationController in the TabBarController embed the TabBarController in another UINavigationController. So you will have: SplitViewController -> Master -> NavCon -> TabBar -> NavCon -> TableViewController.
It's much easier doing like this, but there a bug that I haven't found out how to fix. The navigation bar presented will be that of the TabBarController, not the TableViewController. Any ideas how to fix that?
Subclass TabBarController like this:
- (void)showViewController:(UIViewController *)vc sender:(id)sender
{
if ([self.selectedViewController isKindOfClass:UINavigationController.class])
[self.selectedViewController showViewController:vc sender:sender];
else
[super showViewController:vc sender:sender];
}
- (UIViewController*)separateSecondaryViewControllerForSplitViewController:(UISplitViewController *)splitViewController
{
return [self.selectedViewController separateSecondaryViewControllerForSplitViewController:splitViewController];
}
- (void)collapseSecondaryViewController:(UIViewController *)secondaryViewController forSplitViewController:(UISplitViewController *)splitViewController
{
[self.selectedViewController.navigationController collapseSecondaryViewController:secondaryViewController forSplitViewController:splitViewController];
}
See this question for complete explanation.
Here is an alternative that is based on testing the size classes of the splitViewController :
Use a custom UISplitViewController (subclass)
Override the showDetailViewController operation
Use the traitCollection to determine the class of the UISplitViewController
If the horizontal class is Compact, get the navigationController to call showViewController
Here is the the code of the custom UISplitViewController :
import UIKit
class CustomSplitViewController: UISplitViewController {
override func showDetailViewController(vc: UIViewController!, sender: AnyObject!) {
if (self.traitCollection.horizontalSizeClass == UIUserInterfaceSizeClass.Compact) {
if let tabBarController = self.viewControllers[0] as? UITabBarController {
if let navigationController = tabBarController.selectedViewController as? UINavigationController {
navigationController.showViewController(vc, sender: sender)
return
}
}
}
super.showDetailViewController(vc, sender: sender)
}
}
Do not forget to the set the custom class in the storyboard.
Tested in the simulator of iPhone 6, iPhone 6+ and iPad Air and worked as expected.